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
Make IntegerRangeType->isSubTypeOf analyse Unions #416
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type; | ||
|
||
use PHPStan\TrinaryLogic; | ||
|
||
/** | ||
* Indictates that isSubTypeOf may be called with a UnionType without risking | ||
* infinite recursion. | ||
*/ | ||
interface ImplementsSubTypeOfUnion | ||
{ | ||
|
||
public function isSubTypeOf(Type $type): TrinaryLogic; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type; | ||
|
||
use PHPStan\TrinaryLogic; | ||
use PHPStan\Type\Constant\ConstantIntegerType; | ||
|
||
class IntegerRangeTypeTest extends \PHPStan\Testing\TestCase | ||
{ | ||
|
||
public function dataIsSubTypeOf(): iterable | ||
{ | ||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
new IntegerType(), | ||
TrinaryLogic::createYes(), | ||
]; | ||
|
||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
new ConstantIntegerType(10), | ||
TrinaryLogic::createMaybe(), | ||
]; | ||
|
||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
new ConstantIntegerType(20), | ||
TrinaryLogic::createNo(), | ||
]; | ||
|
||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
IntegerRangeType::fromInterval(0, 15), | ||
TrinaryLogic::createYes(), | ||
]; | ||
|
||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
IntegerRangeType::fromInterval(6, 9), | ||
TrinaryLogic::createMaybe(), | ||
]; | ||
|
||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
new UnionType([new IntegerType(), new StringType()]), | ||
TrinaryLogic::createYes(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It sounds to me like the correct answer here is 'maybe', since the second type might be a string, in which case an integer is not a sub-type of it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
]; | ||
|
||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
new UnionType([new ConstantIntegerType(5), IntegerRangeType::fromInterval(6, 10), new StringType()]), | ||
TrinaryLogic::createYes(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, although this specific union shouldn't even exist in practice, since the |
||
]; | ||
|
||
yield [ | ||
IntegerRangeType::fromInterval(5, 10), | ||
new StringType(), | ||
TrinaryLogic::createNo(), | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider dataIsSubTypeOf | ||
*/ | ||
public function testIsSubTypeOf(IntegerRangeType $type, Type $otherType, TrinaryLogic $expectedResult): void | ||
{ | ||
$actualResult = $type->isSubTypeOf($otherType); | ||
$this->assertSame( | ||
$expectedResult->describe(), | ||
$actualResult->describe(), | ||
sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) | ||
); | ||
} | ||
|
||
} |
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.
(Sorry I messed up the line range selection here... the comment is meant for the entire method
unionIntRanges()
)Code to pretty much do the exact same thing already exists in
TypeCombinator::union()
. In fact, aUnionType
should probably never even contain mergeable integer ranges (or const ints). This is currently not enforced, but the idea is to useTypeCombinator::union(...)
instead ofnew UnionType([...])
whenever in doubt that there might be overlapping types.I understand that in your example you end up with a union type
1|2|3|4
which is then tested againstint<1,4>
. This currently doesn't work properly. I wonder if the cleaner solution wouldn't just be to disallow adjacent integers in unions and enforce the usage of integer ranges (e.g. alwaysint<1,2>
instead of1|2
. This ensures that there's only way one to represent the same thing. With this the sub-type check would automatically start to work correctly.