|
4 | 4 |
|
5 | 5 | use Closure; |
6 | 6 | use PHPStan\DependencyInjection\BleedingEdgeToggle; |
| 7 | +use PHPStan\PhpDoc\TypeStringResolver; |
7 | 8 | use PHPStan\Testing\PHPStanTestCase; |
8 | 9 | use PHPStan\TrinaryLogic; |
9 | 10 | use PHPStan\Type\Accessory\HasOffsetType; |
@@ -909,11 +910,87 @@ public static function dataIsSuperTypeOf(): iterable |
909 | 910 | ]), |
910 | 911 | TrinaryLogic::createYes(), |
911 | 912 | ]; |
| 913 | + |
| 914 | + // definite sealedness tests (bleeding edge) |
| 915 | + |
| 916 | + // both sealed, same keys, compatible values |
| 917 | + yield ['array{a: int, b: string}', 'array{a: int, b: string}', TrinaryLogic::createYes()]; |
| 918 | + |
| 919 | + // both sealed, bigger vs smaller (subset) — sealed requires exact keys |
| 920 | + yield ['array{a: int, b: string}', 'array{a: int}', TrinaryLogic::createNo()]; |
| 921 | + yield ['array{a: int}', 'array{a: int, b: string}', TrinaryLogic::createNo()]; |
| 922 | + |
| 923 | + // both sealed, narrower value |
| 924 | + yield ['array{a: int}', 'array{a: int<0, max>}', TrinaryLogic::createYes()]; |
| 925 | + yield ['array{a: int<0, max>}', 'array{a: int}', TrinaryLogic::createMaybe()]; |
| 926 | + |
| 927 | + // both sealed, optional key in left only |
| 928 | + yield ['array{a: int, b?: string}', 'array{a: int}', TrinaryLogic::createYes()]; |
| 929 | + yield ['array{a: int, b?: string}', 'array{a: int, b: string}', TrinaryLogic::createYes()]; |
| 930 | + |
| 931 | + // both unsealed, compatible known keys + compatible unsealed |
| 932 | + yield ['array{a: int, ...}', 'array{a: int<0, max>, ...}', TrinaryLogic::createYes()]; |
| 933 | + yield ['array{a: int<0, max>, ...}', 'array{a: int, ...}', TrinaryLogic::createMaybe()]; |
| 934 | + |
| 935 | + // both unsealed, bigger known on right (right's extra fits left's unsealed extras) |
| 936 | + yield ['array{a: int, ...}', 'array{a: int, b: string, ...}', TrinaryLogic::createYes()]; |
| 937 | + |
| 938 | + // both unsealed, right has known key left doesn't require; left's unsealed must cover |
| 939 | + yield ['array{a: int, ...<string, string>}', 'array{a: int, b: int, ...<string, string>}', TrinaryLogic::createNo()]; |
| 940 | + yield ['array{a: int, ...<string, string>}', 'array{a: int, b: non-empty-string, ...<string, string>}', TrinaryLogic::createYes()]; |
| 941 | + |
| 942 | + // both unsealed, narrower unsealed value on right |
| 943 | + yield ['array{a: int, ...<string, string>}', 'array{a: int, ...<string, non-empty-string>}', TrinaryLogic::createYes()]; |
| 944 | + yield ['array{a: int, ...<string, non-empty-string>}', 'array{a: int, ...<string, string>}', TrinaryLogic::createMaybe()]; |
| 945 | + |
| 946 | + // both unsealed, narrower unsealed key on right (array-key ⊃ string) |
| 947 | + yield ['array{a: int, ...<array-key, string>}', 'array{a: int, ...<string, string>}', TrinaryLogic::createYes()]; |
| 948 | + yield ['array{a: int, ...<string, string>}', 'array{a: int, ...<array-key, string>}', TrinaryLogic::createMaybe()]; |
| 949 | + |
| 950 | + // both unsealed, incompatible unsealed key types |
| 951 | + yield ['array{...<int, string>}', 'array{...<string, string>}', TrinaryLogic::createNo()]; |
| 952 | + |
| 953 | + // both unsealed, incompatible unsealed value types |
| 954 | + yield ['array{...<int, string>}', 'array{...<int, int>}', TrinaryLogic::createNo()]; |
| 955 | + |
| 956 | + // unsealed vs sealed — sealed's extras must fit unsealed's unsealed |
| 957 | + yield ['array{a: int, ...}', 'array{a: int, b: string}', TrinaryLogic::createYes()]; |
| 958 | + yield ['array{a: int, ...<string, int>}', 'array{a: int, b: string}', TrinaryLogic::createNo()]; |
| 959 | + |
| 960 | + // sealed vs unsealed — unsealed might have extras sealed doesn't allow |
| 961 | + yield ['array{a: int}', 'array{a: int, ...}', TrinaryLogic::createMaybe()]; |
| 962 | + yield ['array{a: int, b: string}', 'array{a: int<0, max>, ...}', TrinaryLogic::createMaybe()]; |
| 963 | + |
| 964 | + // sealed vs unsealed where sealed's keys can't be in unsealed's extras |
| 965 | + yield ['array{a: int}', 'array{...<int, int>}', TrinaryLogic::createNo()]; |
| 966 | + |
| 967 | + // sealed vs unsealed where sealed fits unsealed's extras |
| 968 | + yield ['array{a: int}', 'array{...<string, int>}', TrinaryLogic::createMaybe()]; |
912 | 969 | } |
913 | 970 |
|
| 971 | + /** |
| 972 | + * @param ConstantArrayType|string $type |
| 973 | + * @param Type|string $otherType |
| 974 | + */ |
914 | 975 | #[DataProvider('dataIsSuperTypeOf')] |
915 | | - public function testIsSuperTypeOf(ConstantArrayType $type, Type $otherType, TrinaryLogic $expectedResult): void |
| 976 | + public function testIsSuperTypeOf($type, $otherType, TrinaryLogic $expectedResult): void |
916 | 977 | { |
| 978 | + $bleedingEdgeBackup = BleedingEdgeToggle::isBleedingEdge(); |
| 979 | + BleedingEdgeToggle::setBleedingEdge(true); |
| 980 | + try { |
| 981 | + $resolver = self::getContainer()->getByType(TypeStringResolver::class); |
| 982 | + if (is_string($type)) { |
| 983 | + $resolved = $resolver->resolve($type, null); |
| 984 | + $this->assertInstanceOf(ConstantArrayType::class, $resolved); |
| 985 | + $type = $resolved; |
| 986 | + } |
| 987 | + if (is_string($otherType)) { |
| 988 | + $otherType = $resolver->resolve($otherType, null); |
| 989 | + } |
| 990 | + } finally { |
| 991 | + BleedingEdgeToggle::setBleedingEdge($bleedingEdgeBackup); |
| 992 | + } |
| 993 | + |
917 | 994 | $actualResult = $type->isSuperTypeOf($otherType); |
918 | 995 | $this->assertSame( |
919 | 996 | $expectedResult->describe(), |
|
0 commit comments