Improve comparison operators and fix IntegerRangeType overflowing#372
Improve comparison operators and fix IntegerRangeType overflowing#372ondrejmirtes merged 3 commits intophpstan:masterfrom
Conversation
There was a problem hiding this comment.
Hi, would you care to rewrite this as a new NodeScopeResolverTest::testFileAssert's dataProvider? It would be much clearer to see assertType('false', 1.9 > 2.1) instead of jumping between this file and rule test. Thanks!
src/Type/TypeComparator.php
Outdated
There was a problem hiding this comment.
As a future scope, we should probably think about rewriting this as a method on Type. That way, we could also write implementation of == and also nicely write implementation between different ConstantArrayTypes for example.
Remember, I want to deprecate and get rid of most instanceof *Type. But I'll merge this PR as it is once the other comment is resolved :)
Thank you!
There was a problem hiding this comment.
Right! Actually, I can try to have a go at this tomorrow. So it would be a new method smallerThan() and smallerThanOrEqualTo() method on Type?
There was a problem hiding this comment.
public function smallerThan(self $otherType): TrinaryLogic;
public function smallerThanOrEqual(self $otherType): TrinaryLogic;Most of these method implementations have a special if condition for CompoundType which are implemented by special types like UnionType or IntersectionType, it might look like this:
public function accepts(Type $type, bool $strictTypes): TrinaryLogic
{
if ($type instanceof static) {
return TrinaryLogic::createYes();
}
if ($type instanceof CompoundType) {
return $type->isAcceptedBy($this, $strictTypes);
}
return TrinaryLogic::createNo();
}
public function isSuperTypeOf(Type $type): TrinaryLogic
{
if ($type instanceof self) {
return TrinaryLogic::createYes();
}
if ($type instanceof CompoundType) {
return $type->isSubTypeOf($this);
}
return TrinaryLogic::createNo();
}It's there so that each type can only show interest in types that it should care about. All of these CompoundTypes are handled by a single if in most ordinary Type implementations which makes things easier. I don't know how to name the relevant CompoundType method, maybe isGreaterThan, as an inversion similar to accepts/isAcceptedBy? :) You'll see...
There was a problem hiding this comment.
Please see the update, I hope it is somewhat how you intended it to be. I opted to only add one method, since in almost all implementations < and <= can be handled with the same code. If desired I could still add isSmallerThanOrEqual(), which would then just call isSmallerThan(..., ..., true).
Also I added the is prefix to the method names (IMHO method names should always include at least one verb). This makes it also more similar to a probable future isEqualTo(), which needs to be clearly distinguished from equals().
Finally, I made IntegerRangeType implement CompoundType, I hope that makes sense, because it really needs to handle isGreaterThan().
In particular, this fixes wrong comparisions like: * -1 < null being treated as always true * 1.9 > 1 being treated as always false
|
Hold on with this... I noticed some weirdness. The following code: Now produces: While it originally produces saner output: https://phpstan.org/r/fca7522c-939b-4199-83ca-5db5970f0637 This means that pre/post increment/decrement did not have proper tests :) I will investigate this and add tests. |
|
Okay, that's fixed. I added the tests for pre/post inc/dec and noticed that some situations weren't even handled, I added those as well. |
|
Thank you! I wonder if the current |
|
I'm not entirely sure what you mean... Possibly it's what I'm doing right now: Making My current approach is to add |
|
What I mean is that in theory the original code should stay in MutatingScope: if ($rightType instanceof ConstantIntegerType) {
$rightValue = $rightType->getValue();
if ($node instanceof Expr\BinaryOp\Greater) {
$rightIntervalType = IntegerRangeType::fromInterval($rightValue + 1, null);
} elseif ($node instanceof Expr\BinaryOp\GreaterOrEqual) {
$rightIntervalType = IntegerRangeType::fromInterval($rightValue, null);
} elseif ($node instanceof Expr\BinaryOp\Smaller) {
$rightIntervalType = IntegerRangeType::fromInterval(null, $rightValue - 1);
} else {
$rightIntervalType = IntegerRangeType::fromInterval(null, $rightValue);
}
return $rightIntervalType->isSuperTypeOf($leftType->toInteger())->toBooleanType();
...Instead of adding new |
|
But then it would not work for Given |
|
Of course, you're right :) |
No description provided.