From ca8b5d041384c1284ff12b2172f78e8fe014953f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Aug 2022 10:08:13 +0200 Subject: [PATCH 1/7] take subtracted type into account in MixedType->isArray() --- src/Type/MixedType.php | 16 +++++++ tests/PHPStan/Type/MixedTypeTest.php | 71 ++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index fa06a05bdf..17ec20f8c8 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -408,6 +408,22 @@ public function traverse(callable $cb): Type public function isArray(): TrinaryLogic { + if ($this->subtractedType !== null) { + $arrayType = new ArrayType(new MixedType(), new MixedType()); + + if ($arrayType->isSuperTypeOf($this->subtractedType)->yes()) { + return TrinaryLogic::createNo(); + } + + if ($this->subtractedType instanceof UnionType) { + foreach ($this->subtractedType->getTypes() as $type) { + if ($arrayType->isSuperTypeOf($type)->yes()) { + return TrinaryLogic::createNo(); + } + } + } + } + return TrinaryLogic::createMaybe(); } diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index e639b15708..309deb78b5 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -4,7 +4,9 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use function sprintf; class MixedTypeTest extends PHPStanTestCase @@ -164,4 +166,73 @@ public function testIsSuperTypeOf(MixedType $type, Type $otherType, TrinaryLogic ); } + public function dataSubstractedIsArray(): array + { + return [ + [ + new MixedType(), + new ArrayType(new IntegerType(), new StringType()), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new ArrayType(new MixedType(), new MixedType()), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new ConstantArrayType( + [new ConstantIntegerType(1)], + [new ConstantStringType('hello')], + ), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new UnionType([new FloatType(), new ArrayType(new MixedType(), new MixedType())]), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new UnionType([new FloatType(), new ArrayType(new StringType(), new MixedType())]), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new UnionType([new FloatType(), new IntegerType()]), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new FloatType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(true), + new FloatType(), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsArray + */ + public function testSubstractedIsArray(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isArray(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isArray()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + } From 835d5c00d2219f60cc5ca21813351c004baa2b56 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Aug 2022 10:24:52 +0200 Subject: [PATCH 2/7] fixes after reviews --- src/Type/MixedType.php | 12 +----------- tests/PHPStan/Type/MixedTypeTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 17ec20f8c8..663f134a99 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -409,19 +409,9 @@ public function traverse(callable $cb): Type public function isArray(): TrinaryLogic { if ($this->subtractedType !== null) { - $arrayType = new ArrayType(new MixedType(), new MixedType()); - - if ($arrayType->isSuperTypeOf($this->subtractedType)->yes()) { + if ($this->subtractedType->isSuperTypeOf(new ArrayType(new MixedType(), new MixedType()))->yes()) { return TrinaryLogic::createNo(); } - - if ($this->subtractedType instanceof UnionType) { - foreach ($this->subtractedType->getTypes() as $type) { - if ($arrayType->isSuperTypeOf($type)->yes()) { - return TrinaryLogic::createNo(); - } - } - } } return TrinaryLogic::createMaybe(); diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index 309deb78b5..2a14643dc6 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -172,12 +172,12 @@ public function dataSubstractedIsArray(): array [ new MixedType(), new ArrayType(new IntegerType(), new StringType()), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ], [ new MixedType(), new ArrayType(new StringType(), new StringType()), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ], [ new MixedType(), @@ -190,7 +190,7 @@ public function dataSubstractedIsArray(): array [new ConstantIntegerType(1)], [new ConstantStringType('hello')], ), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ], [ new MixedType(), @@ -200,7 +200,7 @@ public function dataSubstractedIsArray(): array [ new MixedType(), new UnionType([new FloatType(), new ArrayType(new StringType(), new MixedType())]), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ], [ new MixedType(), From 1471aa9109829330192fc85376240f49ac231f1c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Aug 2022 10:56:36 +0200 Subject: [PATCH 3/7] implement subtractable on Mixed->is*String() --- src/Type/MixedType.php | 53 +++++ tests/PHPStan/Type/MixedTypeTest.php | 296 +++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 663f134a99..d083ad5b1d 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -16,6 +16,10 @@ use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Generic\TemplateMixedType; @@ -419,26 +423,75 @@ public function isArray(): TrinaryLogic public function isString(): TrinaryLogic { + if ($this->subtractedType !== null) { + if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) { + return TrinaryLogic::createNo(); + } + } return TrinaryLogic::createMaybe(); } public function isNumericString(): TrinaryLogic { + if ($this->subtractedType !== null) { + $numericString = TypeCombinator::intersect( + new StringType(), + new AccessoryNumericStringType(), + ); + + if ($this->subtractedType->isSuperTypeOf($numericString)->yes()) { + return TrinaryLogic::createNo(); + } + } + return TrinaryLogic::createMaybe(); } public function isNonEmptyString(): TrinaryLogic { + if ($this->subtractedType !== null) { + $nonEmptyString = TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ); + + if ($this->subtractedType->isSuperTypeOf($nonEmptyString)->yes()) { + return TrinaryLogic::createNo(); + } + } + return TrinaryLogic::createMaybe(); } public function isNonFalsyString(): TrinaryLogic { + if ($this->subtractedType !== null) { + $nonFalsyString = TypeCombinator::intersect( + new StringType(), + new AccessoryNonFalsyStringType(), + ); + + if ($this->subtractedType->isSuperTypeOf($nonFalsyString)->yes()) { + return TrinaryLogic::createNo(); + } + } + return TrinaryLogic::createMaybe(); } public function isLiteralString(): TrinaryLogic { + if ($this->subtractedType !== null) { + $literalString = TypeCombinator::intersect( + new StringType(), + new AccessoryLiteralStringType(), + ); + + if ($this->subtractedType->isSuperTypeOf($literalString)->yes()) { + return TrinaryLogic::createNo(); + } + } + return TrinaryLogic::createMaybe(); } diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index 2a14643dc6..f9d5f0c2f7 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -4,6 +4,10 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -235,4 +239,296 @@ public function testSubstractedIsArray(MixedType $mixedType, Type $typeToSubtrac ); } + public function dataSubstractedIsString(): array + { + return [ + [ + new MixedType(), + new StringType(), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNumericStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsString + */ + public function testSubstractedIsString(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isString(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isString()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + + public function dataSubstractedIsNumericString(): array + { + return [ + [ + new MixedType(), + new StringType(), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNumericStringType(), + ), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsNumericString + */ + public function testSubstractedIsNumericString(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isNumericString(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isNumericString()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + + public function dataSubstractedIsNonEmptyString(): array + { + return [ + [ + new MixedType(), + new StringType(), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonFalsyStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNumericStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsNonEmptyString + */ + public function testSubstractedIsNonEmptyString(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isNonEmptyString(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isNonEmptyString()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + + public function dataSubstractedIsNonFalsyString(): array + { + return [ + [ + new MixedType(), + new StringType(), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonFalsyStringType(), + ), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNumericStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsNonFalsyString + */ + public function testSubstractedIsNonFalsyString(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isNonFalsyString(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isNonFalsyString()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + + public function dataSubstractedIsLiteralString(): array + { + return [ + [ + new MixedType(), + new StringType(), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryLiteralStringType(), + ), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonFalsyStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNumericStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsLiteralString + */ + public function testSubstractedIsLiteralString(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isLiteralString(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isLiteralString()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + } From e906f16ed5670803b8619db551f673167684072b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Aug 2022 11:12:28 +0200 Subject: [PATCH 4/7] implement subtractable on Mixed->isIterable() --- src/Type/MixedType.php | 6 ++++ tests/PHPStan/Type/MixedTypeTest.php | 54 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index d083ad5b1d..bcab5983bc 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -340,6 +340,12 @@ public function toArray(): Type public function isIterable(): TrinaryLogic { + if ($this->subtractedType !== null) { + if ($this->subtractedType->isSuperTypeOf(new IterableType(new MixedType(), new MixedType()))->yes()) { + return TrinaryLogic::createNo(); + } + } + return TrinaryLogic::createMaybe(); } diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index f9d5f0c2f7..ee382b4512 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -531,4 +531,58 @@ public function testSubstractedIsLiteralString(MixedType $mixedType, Type $typeT ); } + public function dataSubstractedIsIterable(): array + { + return [ + [ + new MixedType(), + new StringType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryLiteralStringType(), + ), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new IterableType(new MixedType(), new MixedType()), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new IterableType(new StringType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsIterable + */ + public function testSubstractedIsIterable(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isIterable(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isIterable()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + } From f97a37b656ab84de79f29df2063d020e8698ec97 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Aug 2022 13:40:37 +0200 Subject: [PATCH 5/7] fixed is isIterableAtLeastOnce() --- src/Type/MixedType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index bcab5983bc..e4578555a0 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -351,7 +351,7 @@ public function isIterable(): TrinaryLogic public function isIterableAtLeastOnce(): TrinaryLogic { - return TrinaryLogic::createMaybe(); + return $this->isIterable(); } public function getIterableKeyType(): Type From eba74bdfcb095c035ad520f37dcaeb06e6acdc70 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Aug 2022 13:42:58 +0200 Subject: [PATCH 6/7] fixed MixedType->isCallable() --- src/Type/MixedType.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index e4578555a0..f054acec55 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -133,11 +133,10 @@ public function unsetOffset(Type $offsetType): Type public function isCallable(): TrinaryLogic { - if ( - $this->subtractedType !== null - && $this->subtractedType->isCallable()->yes() - ) { - return TrinaryLogic::createNo(); + if ($this->subtractedType !== null) { + if ($this->subtractedType->isSuperTypeOf(new CallableType())->yes()) { + return TrinaryLogic::createNo(); + } } return TrinaryLogic::createMaybe(); From 1fcc5eb1ac44cd6af827681add95251834ff7e49 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Aug 2022 13:52:20 +0200 Subject: [PATCH 7/7] implement subtracted type in Mixed->isOffsetAccessible() --- src/Type/MixedType.php | 12 ++++++ tests/PHPStan/Type/MixedTypeTest.php | 56 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index f054acec55..8b950f5d32 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use ArrayAccess; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\Dummy\DummyConstantReflection; @@ -365,6 +366,17 @@ public function getIterableValueType(): Type public function isOffsetAccessible(): TrinaryLogic { + if ($this->subtractedType !== null) { + $offsetAccessibles = new UnionType([ + new StringType(), + new ArrayType(new MixedType(), new MixedType()), + new ObjectType(ArrayAccess::class), + ]); + + if ($this->subtractedType->isSuperTypeOf($offsetAccessibles)->yes()) { + return TrinaryLogic::createNo(); + } + } return TrinaryLogic::createMaybe(); } diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index ee382b4512..32af9c48e0 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use ArrayAccess; use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLiteralStringType; @@ -585,4 +586,59 @@ public function testSubstractedIsIterable(MixedType $mixedType, Type $typeToSubt ); } + public function dataSubstractedIsOffsetAccessible(): array + { + return [ + [ + new MixedType(), + new ArrayType(new MixedType(), new MixedType()), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new StringType(), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new ObjectType(ArrayAccess::class), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new StringType(), + new ObjectType(ArrayAccess::class), + ]), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new StringType(), + new ObjectType(ArrayAccess::class), + new FloatType(), + ]), + TrinaryLogic::createNo(), + ], + ]; + } + + /** + * @dataProvider dataSubstractedIsOffsetAccessible + */ + public function testSubstractedIsOffsetAccessible(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isOffsetAccessible(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isOffsetAccessible()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + }