diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index fb50126b59..491af5b813 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -93,28 +94,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isArray() - ->and($type->isList()); + return new IsSuperTypeOfResult($type->isArray()->and($type->isList()), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isArray() - ->and($otherType->isList()) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isList()), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -124,7 +133,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 110fbea58c..700063570e 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -17,6 +17,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; @@ -85,26 +86,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isLiteralString(); + return new IsSuperTypeOfResult($type->isLiteralString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isLiteralString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isLiteralString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -114,7 +125,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 195a807dbc..fe57034af1 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -17,6 +17,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -81,26 +82,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isLowercaseString(); + return new IsSuperTypeOfResult($type->isLowercaseString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isLowercaseString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isLowercaseString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -110,7 +121,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index b911c21fbb..6587e85d35 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -18,6 +18,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -83,30 +84,40 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type->isNonFalsyString()->yes()) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isNonEmptyString(); + return new IsSuperTypeOfResult($type->isNonEmptyString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isNonEmptyString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isNonEmptyString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -116,7 +127,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 5703420e9f..2a56264475 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -17,6 +17,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -83,30 +84,40 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isNonFalsyString(); + return new IsSuperTypeOfResult($type->isNonFalsyString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($otherType instanceof AccessoryNonEmptyStringType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $otherType->isNonFalsyString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isNonFalsyString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -116,7 +127,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9cb274782d..1ab8d31eff 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -18,6 +18,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\StringType; use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonCallableTypeTrait; @@ -82,26 +83,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isNumericString(); + return new IsSuperTypeOfResult($type->isNumericString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isNumericString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isNumericString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -119,7 +130,7 @@ public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): return AcceptsResult::createYes(); } - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 98503a13a8..1ced070eb5 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -15,6 +15,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\StringType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; @@ -77,26 +78,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult public function isSuperTypeOf(Type $type): TrinaryLogic { - return $type->hasMethod($this->methodName); + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + return new IsSuperTypeOfResult($type->hasMethod($this->methodName), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($this->isCallable()->yes() && $otherType->isCallable()->yes()) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); + $limit = IsSuperTypeOfResult::createYes(); } else { - $limit = TrinaryLogic::createMaybe(); + $limit = IsSuperTypeOfResult::createMaybe(); } - return $limit->and($otherType->hasMethod($this->methodName)); + return $limit->and(new IsSuperTypeOfResult($otherType->hasMethod($this->methodName), [])); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -106,7 +117,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index bcd5a93122..f1ccf3c2a2 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Traits\MaybeArrayTypeTrait; @@ -95,23 +96,33 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)); + return new IsSuperTypeOfResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isOffsetAccessible() - ->and($otherType->hasOffsetValueType($this->offsetType)) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + $result = new IsSuperTypeOfResult($otherType->isOffsetAccessible()->and($otherType->hasOffsetValueType($this->offsetType)), []); + + return $result + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -121,7 +132,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 651d81560a..600fa6ca63 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -16,6 +16,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Traits\MaybeArrayTypeTrait; @@ -91,34 +92,44 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $type->isAcceptedWithReasonBy($this, $strictTypes); } - return new AcceptsResult( - $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)) - ->and($this->valueType->accepts($type->getOffsetValueType($this->offsetType), $strictTypes)), - [], - ); + $result = new AcceptsResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); + + return $result->and($this->valueType->acceptsWithReason($type->getOffsetValueType($this->offsetType), $strictTypes)); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)) - ->and($this->valueType->isSuperTypeOf($type->getOffsetValueType($this->offsetType))); + + $result = new IsSuperTypeOfResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); + + return $result + ->and($this->valueType->isSuperTypeOfWithReason($type->getOffsetValueType($this->offsetType))); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isOffsetAccessible() - ->and($otherType->hasOffsetValueType($this->offsetType)) - ->and($otherType->getOffsetValueType($this->offsetType)->isSuperTypeOf($this->valueType)) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + $result = new IsSuperTypeOfResult($otherType->isOffsetAccessible()->and($otherType->hasOffsetValueType($this->offsetType)), []); + + return $result + ->and($otherType->getOffsetValueType($this->offsetType)->isSuperTypeOfWithReason($this->valueType)) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -128,7 +139,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index f508447135..372bcf70f1 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -11,6 +11,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; @@ -79,22 +80,32 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult public function isSuperTypeOf(Type $type): TrinaryLogic { - return $type->hasProperty($this->propertyName); + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + return new IsSuperTypeOfResult($type->hasProperty($this->propertyName), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); + $limit = IsSuperTypeOfResult::createYes(); } else { - $limit = TrinaryLogic::createMaybe(); + $limit = IsSuperTypeOfResult::createMaybe(); } - return $limit->and($otherType->hasProperty($this->propertyName)); + return $limit->and(new IsSuperTypeOfResult($otherType->hasProperty($this->propertyName), [])); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -104,7 +115,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 084cc28d3c..c822edcc7a 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -90,28 +91,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isArray() - ->and($type->isIterableAtLeastOnce()); + return new IsSuperTypeOfResult($type->isArray()->and($type->isIterableAtLeastOnce()), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isArray() - ->and($otherType->isIterableAtLeastOnce()) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isIterableAtLeastOnce()), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -121,7 +130,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 7bb8e5213b..4d39431f7b 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -86,28 +87,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isArray() - ->and($type->isOversizedArray()); + return new IsSuperTypeOfResult($type->isArray()->and($type->isOversizedArray()), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isArray() - ->and($otherType->isOversizedArray()) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isOversizedArray()), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -117,7 +126,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index c1d163d97c..01ca997eee 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -128,17 +128,22 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getItemType()->isSuperTypeOf($type->getItemType()) - ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); + return $this->getItemType()->isSuperTypeOfWithReason($type->getItemType()) + ->and($this->getIterableKeyType()->isSuperTypeOfWithReason($type->getIterableKeyType())); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 82b3645a27..d9bbea57e6 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -140,21 +140,26 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $type->isAcceptedWithReasonBy($this, $strictTypes); } - return $this->isSuperTypeOfInternal($type, true); + return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType && !$type instanceof self) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $this->isSuperTypeOfInternal($type, false)->result; + return $this->isSuperTypeOfInternal($type, false); } - private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): AcceptsResult + private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult { - $isCallable = new AcceptsResult($type->isCallable(), []); + $isCallable = new IsSuperTypeOfResult($type->isCallable(), []); if ($isCallable->no()) { return $isCallable; } @@ -171,7 +176,7 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): Accep $typePure = $typePure->and($variant->isPure()); } - return $isCallable->and(new AcceptsResult($typePure, [])); + return $isCallable->and(new IsSuperTypeOfResult($typePure, [])); } return $isCallable; @@ -195,13 +200,18 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): Accep } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isCallable() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isCallable(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -211,7 +221,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index 29f416d3aa..aa28e59fff 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -16,7 +16,7 @@ public static function isParametersAcceptorSuperTypeOf( CallableParametersAcceptor $ours, CallableParametersAcceptor $theirs, bool $treatMixedAsAny, - ): AcceptsResult + ): IsSuperTypeOfResult { $theirParameters = $theirs->getParameters(); $ourParameters = $ours->getParameters(); @@ -40,7 +40,7 @@ public static function isParametersAcceptorSuperTypeOf( } } - $result = AcceptsResult::createYes(); + $result = IsSuperTypeOfResult::createYes(); foreach ($theirParameters as $i => $theirParameter) { $parameterDescription = $theirParameter->getName() === '' ? sprintf('#%d', $i + 1) : sprintf('#%d $%s', $i + 1, $theirParameter->getName()); if (!isset($ourParameters[$i])) { @@ -48,7 +48,7 @@ public static function isParametersAcceptorSuperTypeOf( continue; } - $accepts = new AcceptsResult(TrinaryLogic::createNo(), [ + $accepts = new IsSuperTypeOfResult(TrinaryLogic::createNo(), [ sprintf( 'Parameter %s of passed callable is required but accepting callable does not have that parameter. It will be called without it.', $parameterDescription, @@ -62,7 +62,7 @@ public static function isParametersAcceptorSuperTypeOf( $ourParameterType = $ourParameter->getType(); if ($ourParameter->isOptional() && !$theirParameter->isOptional()) { - $accepts = new AcceptsResult(TrinaryLogic::createNo(), [ + $accepts = new IsSuperTypeOfResult(TrinaryLogic::createNo(), [ sprintf( 'Parameter %s of passed callable is required but the parameter of accepting callable is optional. It might be called without it.', $parameterDescription, @@ -73,13 +73,14 @@ public static function isParametersAcceptorSuperTypeOf( if ($treatMixedAsAny) { $isSuperType = $theirParameter->getType()->acceptsWithReason($ourParameterType, true); + $isSuperType = new IsSuperTypeOfResult($isSuperType->result, $isSuperType->reasons); } else { - $isSuperType = new AcceptsResult($theirParameter->getType()->isSuperTypeOf($ourParameterType), []); + $isSuperType = $theirParameter->getType()->isSuperTypeOfWithReason($ourParameterType); } if ($isSuperType->maybe()) { $verbosity = VerbosityLevel::getRecommendedLevelByType($theirParameter->getType(), $ourParameterType); - $isSuperType = new AcceptsResult($isSuperType->result, array_merge($isSuperType->reasons, [ + $isSuperType = new IsSuperTypeOfResult($isSuperType->result, array_merge($isSuperType->reasons, [ sprintf( 'Type %s of parameter %s of passed callable needs to be same or wider than parameter type %s of accepting callable.', $theirParameter->getType()->describe($verbosity), @@ -93,21 +94,22 @@ public static function isParametersAcceptorSuperTypeOf( } if (!$treatMixedAsAny && $theirParameterCount < $ourParameterCount) { - $result = $result->and(AcceptsResult::createMaybe()); + $result = $result->and(IsSuperTypeOfResult::createMaybe()); } $theirReturnType = $theirs->getReturnType(); if ($treatMixedAsAny) { $isReturnTypeSuperType = $ours->getReturnType()->acceptsWithReason($theirReturnType, true); + $isReturnTypeSuperType = new IsSuperTypeOfResult($isReturnTypeSuperType->result, $isReturnTypeSuperType->reasons); } else { - $isReturnTypeSuperType = new AcceptsResult($ours->getReturnType()->isSuperTypeOf($theirReturnType), []); + $isReturnTypeSuperType = $ours->getReturnType()->isSuperTypeOfWithReason($theirReturnType); } $pure = $ours->isPure(); if ($pure->yes()) { - $result = $result->and(new AcceptsResult($theirs->isPure(), [])); + $result = $result->and(new IsSuperTypeOfResult($theirs->isPure(), [])); } elseif ($pure->no()) { - $result = $result->and(new AcceptsResult($theirs->isPure()->negate(), [])); + $result = $result->and(new IsSuperTypeOfResult($theirs->isPure()->negate(), [])); } return $result->and($isReturnTypeSuperType); diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index eaca9d4572..2f58e27d15 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -36,12 +36,17 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isClassStringType(); + return new IsSuperTypeOfResult($type->isClassStringType(), []); } public function isString(): TrinaryLogic diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index e55847aae0..bbe6d5a73e 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -196,19 +196,24 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $this->objectType->acceptsWithReason($type, $strictTypes); } - return $this->isSuperTypeOfInternal($type, true); + return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $this->isSuperTypeOfInternal($type, false)->result; + return $this->isSuperTypeOfInternal($type, false); } - private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): AcceptsResult + private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult { if ($type instanceof self) { return CallableTypeHelper::isParametersAcceptorSuperTypeOf( @@ -219,10 +224,10 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): Accep } if ($type->getObjectClassNames() === [Closure::class]) { - return AcceptsResult::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return new AcceptsResult($this->objectType->isSuperTypeOf($type), []); + return $this->objectType->isSuperTypeOfWithReason($type); } public function equals(Type $type): bool diff --git a/src/Type/CompoundType.php b/src/Type/CompoundType.php index ea9b2f4660..b3080ceb91 100644 --- a/src/Type/CompoundType.php +++ b/src/Type/CompoundType.php @@ -10,6 +10,15 @@ interface CompoundType extends Type public function isSubTypeOf(Type $otherType): TrinaryLogic; + /** + * This is like isSubTypeOf() but gives reasons + * why the type was not/might not be accepted in some non-intuitive scenarios. + * + * In PHPStan 2.0 this method will be removed and the return type of isSubTypeOf() + * will change to IsSuperTypeOfResult. + */ + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult; + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic; public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult; diff --git a/src/Type/ConditionalType.php b/src/Type/ConditionalType.php index aa5d8af0a6..f69d3633f7 100644 --- a/src/Type/ConditionalType.php +++ b/src/Type/ConditionalType.php @@ -62,10 +62,15 @@ public function isNegated(): bool } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->if->isSuperTypeOf($type->if) - ->and($this->else->isSuperTypeOf($type->else)); + return $this->if->isSuperTypeOfWithReason($type->if) + ->and($this->else->isSuperTypeOfWithReason($type->else)); } return $this->isSuperTypeOfDefault($type); diff --git a/src/Type/ConditionalTypeForParameter.php b/src/Type/ConditionalTypeForParameter.php index 57c2fe5d8d..72d7f0f5a2 100644 --- a/src/Type/ConditionalTypeForParameter.php +++ b/src/Type/ConditionalTypeForParameter.php @@ -76,10 +76,15 @@ public function toConditional(Type $subject): Type } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->if->isSuperTypeOf($type->if) - ->and($this->else->isSuperTypeOf($type->else)); + return $this->if->isSuperTypeOfWithReason($type->if) + ->and($this->else->isSuperTypeOfWithReason($type->else)); } return $this->isSuperTypeOfDefault($type); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 259952b41b..6838aa336b 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -38,6 +38,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -363,10 +364,15 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { if (count($this->keyTypes) === 0) { - return $type->isIterableAtLeastOnce()->negate(); + return new IsSuperTypeOfResult($type->isIterableAtLeastOnce()->negate(), []); } $results = []; @@ -374,44 +380,44 @@ public function isSuperTypeOf(Type $type): TrinaryLogic $hasOffset = $type->hasOffsetValueType($keyType); if ($hasOffset->no()) { if (!$this->isOptionalKey($i)) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } - $results[] = TrinaryLogic::createYes(); + $results[] = IsSuperTypeOfResult::createYes(); continue; } elseif ($hasOffset->maybe() && !$this->isOptionalKey($i)) { - $results[] = TrinaryLogic::createMaybe(); + $results[] = IsSuperTypeOfResult::createMaybe(); } - $isValueSuperType = $this->valueTypes[$i]->isSuperTypeOf($type->getOffsetValueType($keyType)); + $isValueSuperType = $this->valueTypes[$i]->isSuperTypeOfWithReason($type->getOffsetValueType($keyType)); if ($isValueSuperType->no()) { - return TrinaryLogic::createNo(); + return $isValueSuperType; } $results[] = $isValueSuperType; } - return TrinaryLogic::createYes()->and(...$results); + return IsSuperTypeOfResult::createYes()->and(...$results); } if ($type instanceof ArrayType) { - $result = TrinaryLogic::createMaybe(); + $result = IsSuperTypeOfResult::createMaybe(); if (count($this->keyTypes) === 0) { return $result; } - $isKeySuperType = $this->getKeyType()->isSuperTypeOf($type->getKeyType()); + $isKeySuperType = $this->getKeyType()->isSuperTypeOfWithReason($type->getKeyType()); if ($isKeySuperType->no()) { - return TrinaryLogic::createNo(); + return $isKeySuperType; } - return $result->and($isKeySuperType, $this->getItemType()->isSuperTypeOf($type->getItemType())); + return $result->and($isKeySuperType, $this->getItemType()->isSuperTypeOfWithReason($type->getItemType())); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index 9226acacc2..603281d734 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -11,6 +11,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait; use PHPStan\Type\Traits\ConstantScalarTypeTrait; use PHPStan\Type\Type; @@ -38,30 +39,35 @@ public function getValue(): int } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); + return $this->value === $type->value ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createNo(); } if ($type instanceof IntegerRangeType) { $min = $type->getMin(); $max = $type->getMax(); if (($min === null || $min <= $this->value) && ($max === null || $this->value <= $max)) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function describe(VerbosityLevel $level): string diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 382911e69d..6350d17cef 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -33,6 +33,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; @@ -147,11 +148,16 @@ private function export(string $value): string } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof GenericClassStringType) { $genericType = $type->getGenericType(); if ($genericType instanceof MixedType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($genericType instanceof StaticType) { $genericType = $genericType->getStaticObjectType(); @@ -164,34 +170,34 @@ public function isSuperTypeOf(Type $type): TrinaryLogic // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType // uncertainty into account. if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); + $isSuperType = $genericType->getBound()->isSuperTypeOfWithReason($objectType); } else { - $isSuperType = $genericType->isSuperTypeOf($objectType); + $isSuperType = $genericType->isSuperTypeOfWithReason($objectType); } // Explicitly handle the uncertainty for Yes & Maybe. if ($isSuperType->yes()) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($type instanceof ClassStringType) { - return $this->isClassStringType()->yes() ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); + return $this->isClassStringType()->yes() ? IsSuperTypeOfResult::createMaybe() : IsSuperTypeOfResult::createNo(); } if ($type instanceof self) { - return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); + return $this->value === $type->value ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createNo(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function isCallable(): TrinaryLogic diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 42d8f4afc2..cf91e6e2d9 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -16,6 +16,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\GeneralizePrecision; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectType; use PHPStan\Type\SubtractableType; use PHPStan\Type\Type; @@ -65,34 +66,39 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSuperTypeOf($type), []); + return $this->isSuperTypeOfWithReason($type)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createFromBoolean( + return IsSuperTypeOfResult::createFromBoolean( $this->enumCaseName === $type->enumCaseName && $this->getClassName() === $type->getClassName(), ); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ( $type instanceof SubtractableType && $type->getSubtractedType() !== null ) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); + $isSuperType = $type->getSubtractedType()->isSuperTypeOfWithReason($this); if ($isSuperType->yes()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } } $parent = new parent($this->getClassName(), $this->getSubtractedType(), $this->getClassReflection()); - return $parent->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe()); + return $parent->isSuperTypeOfWithReason($type)->and(IsSuperTypeOfResult::createMaybe()); } public function subtract(Type $type): Type diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 0ffa96e002..bd5b2a9082 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -83,16 +83,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index fd6ecd5f03..893c13d2c5 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -12,6 +12,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; @@ -86,15 +87,20 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof ConstantStringType) { $genericType = $this->type; if ($genericType instanceof MixedType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($genericType instanceof StaticType) { @@ -108,23 +114,23 @@ public function isSuperTypeOf(Type $type): TrinaryLogic // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType // uncertainty into account. if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); + $isSuperType = $genericType->getBound()->isSuperTypeOfWithReason($objectType); } else { - $isSuperType = $genericType->isSuperTypeOf($objectType); + $isSuperType = $genericType->isSuperTypeOfWithReason($objectType); } if (!$type->isClassStringType()->yes()) { - $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); + $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe()); } return $isSuperType; } elseif ($type instanceof self) { - return $this->type->isSuperTypeOf($type->type); + return $this->type->isSuperTypeOfWithReason($type->type); } elseif ($type instanceof StringType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function traverse(callable $cb): Type diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 775134aab6..24e4aa8f9f 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -18,6 +18,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; @@ -129,21 +130,26 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $type->isAcceptedWithReasonBy($this, $strictTypes); } - return $this->isSuperTypeOfInternal($type, true); + return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $this->isSuperTypeOfInternal($type, false)->result; + return $this->isSuperTypeOfInternal($type, false); } - private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): AcceptsResult + private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSuperTypeOfResult { - $nakedSuperTypeOf = new AcceptsResult(parent::isSuperTypeOf($type), []); + $nakedSuperTypeOf = parent::isSuperTypeOfWithReason($type); if ($nakedSuperTypeOf->no()) { return $nakedSuperTypeOf; } @@ -161,11 +167,11 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): Accept return $nakedSuperTypeOf; } - return $nakedSuperTypeOf->and(AcceptsResult::createMaybe()); + return $nakedSuperTypeOf->and(IsSuperTypeOfResult::createMaybe()); } if (count($this->types) !== count($ancestor->types)) { - return AcceptsResult::createNo(); + return IsSuperTypeOfResult::createNo(); } $classReflection = $this->getClassReflection(); @@ -197,14 +203,14 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): Accept $results[] = $templateType->isValidVarianceWithReason($this->types[$i], $ancestor->types[$i]); } - $results[] = AcceptsResult::createFromBoolean($thisVariance->validPosition($ancestorVariance)); + $results[] = IsSuperTypeOfResult::createFromBoolean($thisVariance->validPosition($ancestorVariance)); } if (count($results) === 0) { return $nakedSuperTypeOf; } - $result = AcceptsResult::createYes(); + $result = IsSuperTypeOfResult::createYes(); foreach ($results as $innerResult) { $result = $result->and($innerResult); } diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 3363818673..132cb20d6b 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -47,7 +47,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - $isSuperType = new AcceptsResult($this->isSuperTypeOf($acceptingType), []); + $isSuperType = $this->isSuperTypeOfWithReason($acceptingType)->toAcceptsResult(); if ($isSuperType->no()) { return $isSuperType; } diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php index 6ae56cc228..071475e215 100644 --- a/src/Type/Generic/TemplateStrictMixedType.php +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -45,7 +45,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } } diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index 7661078ca1..7398fe836f 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -3,8 +3,8 @@ namespace PHPStan\Type\Generic; use PHPStan\TrinaryLogic; -use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Type; /** @api */ @@ -24,7 +24,7 @@ public function isArgument(): bool; public function isValidVariance(Type $a, Type $b): TrinaryLogic; - public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult; + public function isValidVarianceWithReason(Type $a, Type $b): IsSuperTypeOfResult; public function getVariance(): TemplateTypeVariance; diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 66fd32a2fd..b344be3690 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -9,6 +9,7 @@ use PHPStan\Type\AcceptsResult; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\SubtractableType; @@ -98,7 +99,7 @@ public function isValidVariance(Type $a, Type $b): TrinaryLogic return $this->isValidVarianceWithReason($a, $b)->result; } - public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult + public function isValidVarianceWithReason(Type $a, Type $b): IsSuperTypeOfResult { return $this->variance->isValidVarianceWithReason($this, $a, $b); } @@ -206,20 +207,30 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof TemplateType || $type instanceof IntersectionType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $this->getBound()->isSuperTypeOf($type) - ->and(TrinaryLogic::createMaybe()); + return $this->getBound()->isSuperTypeOfWithReason($type) + ->and(IsSuperTypeOfResult::createMaybe()); } public function isSubTypeOf(Type $type): TrinaryLogic + { + return $this->isSubTypeOfWithReason($type)->result; + } + + public function isSubTypeOfWithReason(Type $type): IsSuperTypeOfResult { /** @var TBound $bound */ $bound = $this->getBound(); @@ -229,19 +240,19 @@ public function isSubTypeOf(Type $type): TrinaryLogic && !$type instanceof TemplateType && ($type instanceof UnionType || $type instanceof IntersectionType) ) { - return $type->isSuperTypeOf($this); + return $type->isSuperTypeOfWithReason($this); } if (!$type instanceof TemplateType) { - return $type->isSuperTypeOf($this->getBound()); + return $type->isSuperTypeOfWithReason($this->getBound()); } if ($this->getScope()->equals($type->getScope()) && $this->getName() === $type->getName()) { - return $type->getBound()->isSuperTypeOf($this->getBound()); + return $type->getBound()->isSuperTypeOfWithReason($this->getBound()); } - return $type->getBound()->isSuperTypeOf($this->getBound()) - ->and(TrinaryLogic::createMaybe()); + return $type->getBound()->isSuperTypeOfWithReason($this->getBound()) + ->and(IsSuperTypeOfResult::createMaybe()); } public function toArrayKey(): Type diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 786c61302c..c48a457b19 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -5,8 +5,8 @@ use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\AcceptsResult; use PHPStan\Type\BenevolentUnionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; @@ -134,30 +134,30 @@ public function isValidVariance(Type $a, Type $b): TrinaryLogic return $this->isValidVarianceWithReason(null, $a, $b)->result; } - public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): AcceptsResult + public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): IsSuperTypeOfResult { if ($b instanceof NeverType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($a instanceof MixedType && !$a instanceof TemplateType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($a instanceof BenevolentUnionType) { if (!$a->isSuperTypeOf($b)->no()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } } if ($b instanceof BenevolentUnionType) { if (!$b->isSuperTypeOf($a)->no()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } } if ($b instanceof MixedType && !$b instanceof TemplateType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($this->invariant()) { @@ -177,19 +177,19 @@ public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, } } - return new AcceptsResult(TrinaryLogic::createFromBoolean($result), $reasons); + return new IsSuperTypeOfResult(TrinaryLogic::createFromBoolean($result), $reasons); } if ($this->covariant()) { - return new AcceptsResult($a->isSuperTypeOf($b), []); + return $a->isSuperTypeOfWithReason($b); } if ($this->contravariant()) { - return new AcceptsResult($b->isSuperTypeOf($a), []); + return $b->isSuperTypeOfWithReason($a); } if ($this->bivariant()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } throw new ShouldNotHappenException(); diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 8a9414fbee..1b914804dc 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -15,6 +15,7 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use function array_filter; +use function array_map; use function assert; use function ceil; use function count; @@ -209,7 +210,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof parent) { - return new AcceptsResult($this->isSuperTypeOf($type), []); + return $this->isSuperTypeOfWithReason($type)->toAcceptsResult(); } if ($type instanceof CompoundType) { @@ -220,6 +221,11 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self || $type instanceof ConstantIntegerType) { if ($type instanceof self) { @@ -231,48 +237,53 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if (self::isDisjoint($this->min, $this->max, $typeMin, $typeMax)) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ( ($this->min === null || $typeMin !== null && $this->min <= $typeMin) && ($this->max === null || $typeMax !== null && $this->max >= $typeMax) ) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof parent) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($otherType instanceof UnionType) { - return $this->isSubTypeOfUnion($otherType); + return $this->isSubTypeOfUnionWithReason($otherType); } if ($otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } - private function isSubTypeOfUnion(UnionType $otherType): TrinaryLogic + private function isSubTypeOfUnionWithReason(UnionType $otherType): IsSuperTypeOfResult { if ($this->min !== null && $this->max !== null) { $matchingConstantIntegers = array_filter( @@ -281,11 +292,11 @@ private function isSubTypeOfUnion(UnionType $otherType): TrinaryLogic ); if (count($matchingConstantIntegers) === ($this->max - $this->min + 1)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } } - return TrinaryLogic::createNo()->lazyOr($otherType->getTypes(), fn (Type $innerType) => $this->isSubTypeOf($innerType)); + return IsSuperTypeOfResult::createNo()->or(...array_map(fn (Type $innerType) => $this->isSubTypeOfWithReason($innerType), $otherType->getTypes())); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -295,7 +306,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index bb3eaab0e7..140e45a8ed 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -221,28 +221,38 @@ public function acceptsWithReason(Type $otherType, bool $strictTypes): AcceptsRe } public function isSuperTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($otherType)->result; + } + + public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType && $this->equals($otherType)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($otherType instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createYes()->lazyAnd($this->getTypes(), static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType)); + return IsSuperTypeOfResult::createYes()->and(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOfWithReason($otherType), $this->types)); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if (($otherType instanceof self || $otherType instanceof UnionType) && !$otherType instanceof TemplateType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - $result = TrinaryLogic::lazyMaxMin($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType)); + $result = IsSuperTypeOfResult::maxMin(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOfWithReason($innerType), $this->types)); if ($this->isOversizedArray()->yes()) { if (!$result->no()) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } } diff --git a/src/Type/IsSuperTypeOfResult.php b/src/Type/IsSuperTypeOfResult.php new file mode 100644 index 0000000000..30e9fe6acc --- /dev/null +++ b/src/Type/IsSuperTypeOfResult.php @@ -0,0 +1,159 @@ + $reasons + */ + public function __construct( + public readonly TrinaryLogic $result, + public readonly array $reasons, + ) + { + } + + public function yes(): bool + { + return $this->result->yes(); + } + + public function maybe(): bool + { + return $this->result->maybe(); + } + + public function no(): bool + { + return $this->result->no(); + } + + public static function createYes(): self + { + return new self(TrinaryLogic::createYes(), []); + } + + /** + * @param list $reasons + */ + public static function createNo(array $reasons = []): self + { + return new self(TrinaryLogic::createNo(), $reasons); + } + + public static function createMaybe(): self + { + return new self(TrinaryLogic::createMaybe(), []); + } + + public static function createFromBoolean(bool $value): self + { + return new self(TrinaryLogic::createFromBoolean($value), []); + } + + public function toAcceptsResult(): AcceptsResult + { + return new AcceptsResult($this->result, $this->reasons); + } + + public function and(self ...$others): self + { + $results = []; + $reasons = []; + foreach ($others as $other) { + $results[] = $other->result; + $reasons[] = $other->reasons; + } + + return new self( + $this->result->and(...$results), + array_values(array_unique(array_merge($this->reasons, ...$reasons))), + ); + } + + public function or(self ...$others): self + { + $results = []; + $reasons = []; + foreach ($others as $other) { + $results[] = $other->result; + $reasons[] = $other->reasons; + } + + return new self( + $this->result->or(...$results), + array_values(array_unique(array_merge($this->reasons, ...$reasons))), + ); + } + + /** + * @param callable(string): string $cb + */ + public function decorateReasons(callable $cb): self + { + $reasons = []; + foreach ($this->reasons as $reason) { + $reasons[] = $cb($reason); + } + + return new self($this->result, $reasons); + } + + public static function extremeIdentity(self ...$operands): self + { + if ($operands === []) { + throw new ShouldNotHappenException(); + } + + $result = TrinaryLogic::extremeIdentity(...array_map(static fn (self $result) => $result->result, $operands)); + + return new self($result, self::mergeReasons($operands)); + } + + public static function maxMin(self ...$operands): self + { + if ($operands === []) { + throw new ShouldNotHappenException(); + } + + $result = TrinaryLogic::maxMin(...array_map(static fn (self $result) => $result->result, $operands)); + + return new self($result, self::mergeReasons($operands)); + } + + public function negate(): self + { + return new self($this->result->negate(), $this->reasons); + } + + /** + * @param array $operands + * + * @return list + */ + private static function mergeReasons(array $operands): array + { + $reasons = []; + foreach ($operands as $operand) { + foreach ($operand->reasons as $reason) { + $reasons[] = $reason; + } + } + + return $reasons; + } + +} diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 6ef8ff5ee3..662d2e1b0c 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -101,14 +101,19 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isIterable() - ->and($this->getIterableValueType()->isSuperTypeOf($type->getIterableValueType())) - ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); + return (new IsSuperTypeOfResult($type->isIterable(), [])) + ->and($this->getIterableValueType()->isSuperTypeOfWithReason($type->getIterableValueType())) + ->and($this->getIterableKeyType()->isSuperTypeOfWithReason($type->getIterableKeyType())); } public function isSuperTypeOfMixed(Type $type): TrinaryLogic @@ -140,9 +145,14 @@ private function isNestedTypeSuperTypeOf(Type $a, Type $b): TrinaryLogic } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOf(new UnionType([ + return $otherType->isSuperTypeOfWithReason(new UnionType([ new ArrayType($this->keyType, $this->itemType), new IntersectionType([ new ObjectType(Traversable::class), @@ -152,19 +162,19 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic } if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); + $limit = IsSuperTypeOfResult::createYes(); } else { - $limit = TrinaryLogic::createMaybe(); + $limit = IsSuperTypeOfResult::createMaybe(); } if ($otherType->isConstantArray()->yes() && $otherType->isIterableAtLeastOnce()->no()) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } return $limit->and( - $otherType->isIterable(), - $otherType->getIterableValueType()->isSuperTypeOf($this->itemType), - $otherType->getIterableKeyType()->isSuperTypeOf($this->keyType), + new IsSuperTypeOfResult($otherType->isIterable(), []), + $otherType->getIterableValueType()->isSuperTypeOfWithReason($this->itemType), + $otherType->getIterableKeyType()->isSuperTypeOfWithReason($this->keyType), ); } @@ -175,7 +185,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 7f48131262..df6bee28b8 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -45,16 +45,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 777a47b94d..20117d68b3 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -137,24 +137,29 @@ public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->subtractedType === null || $type instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof self) { if ($type->subtractedType === null) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); + $isSuperType = $type->subtractedType->isSuperTypeOfWithReason($this->subtractedType); if ($isSuperType->yes()) { - return TrinaryLogic::createYes(); + return $isSuperType; } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return $this->subtractedType->isSuperTypeOf($type)->negate(); + return $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); } public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type @@ -326,19 +331,24 @@ public function equals(Type $type): bool } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOf($otherType); + $isSuperType = $this->subtractedType->isSuperTypeOfWithReason($otherType); if ($isSuperType->yes()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -348,7 +358,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - $isSuperType = new AcceptsResult($this->isSuperTypeOf($acceptingType), []); + $isSuperType = $this->isSuperTypeOfWithReason($acceptingType)->toAcceptsResult(); if ($isSuperType->no()) { return $isSuperType; } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index f9e2288e28..45f9cb05f3 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -83,12 +83,17 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool @@ -98,7 +103,12 @@ public function equals(Type $type): bool public function isSubTypeOf(Type $otherType): TrinaryLogic { - return TrinaryLogic::createYes(); + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + { + return IsSuperTypeOfResult::createYes(); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -108,7 +118,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function describe(VerbosityLevel $level): string diff --git a/src/Type/NonAcceptingNeverType.php b/src/Type/NonAcceptingNeverType.php index 3eaddf53cb..861165cdf2 100644 --- a/src/Type/NonAcceptingNeverType.php +++ b/src/Type/NonAcceptingNeverType.php @@ -15,15 +15,20 @@ public function __construct() } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult diff --git a/src/Type/NullType.php b/src/Type/NullType.php index ab50c2040e..7b60d5d3cb 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -91,16 +91,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index a00324259a..af1e4d67d4 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -238,13 +238,18 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); @@ -257,13 +262,13 @@ public function isSuperTypeOf(Type $type): TrinaryLogic continue; } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - $result = TrinaryLogic::createYes(); + $result = IsSuperTypeOfResult::createYes(); $scope = new OutOfClassScope(); foreach ($this->properties as $propertyName => $propertyType) { - $hasProperty = $type->hasProperty($propertyName); + $hasProperty = new IsSuperTypeOfResult($type->hasProperty($propertyName), []); if ($hasProperty->no()) { if (in_array($propertyName, $this->optionalProperties, true)) { continue; @@ -271,7 +276,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return $hasProperty; } if ($hasProperty->maybe() && in_array($propertyName, $this->optionalProperties, true)) { - $hasProperty = TrinaryLogic::createYes(); + $hasProperty = IsSuperTypeOfResult::createYes(); } $result = $result->and($hasProperty); @@ -283,26 +288,26 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if (!$otherProperty->isPublic()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($otherProperty->isStatic()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if (!$otherProperty->isReadable()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } $otherPropertyType = $otherProperty->getReadableType(); - $isSuperType = $propertyType->isSuperTypeOf($otherPropertyType); + $isSuperType = $propertyType->isSuperTypeOfWithReason($otherPropertyType); if ($isSuperType->no()) { return $isSuperType; } $result = $result->and($isSuperType); } - return $result->and($type->isObject()); + return $result->and(new IsSuperTypeOfResult($type->isObject(), [])); } public function equals(Type $type): bool diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 31a9613e91..3e8ac1371f 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -71,7 +71,7 @@ class ObjectType implements TypeWithClassName, SubtractableType private ?Type $subtractedType; - /** @var array> */ + /** @var array> */ private static array $superTypes = []; private ?self $cachedParent = null; @@ -314,10 +314,15 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { $thatClassNames = $type->getObjectClassNames(); if (!$type instanceof CompoundType && $thatClassNames === [] && !$type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } $thisDescription = $this->describeCache(); @@ -333,31 +338,31 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if ($type instanceof CompoundType) { - return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this); + return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOfWithReason($this); } if ($type instanceof ClosureType) { - return self::$superTypes[$thisDescription][$description] = $this->isInstanceOf(Closure::class); + return self::$superTypes[$thisDescription][$description] = new IsSuperTypeOfResult($this->isInstanceOf(Closure::class), []); } if ($type instanceof ObjectWithoutClassType) { if ($type->getSubtractedType() !== null) { $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } } - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } - $transformResult = static fn (TrinaryLogic $result) => $result; + $transformResult = static fn (IsSuperTypeOfResult $result) => $result; if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOf($type); + $isSuperType = $this->subtractedType->isSuperTypeOfWithReason($type); if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } if ($isSuperType->maybe()) { - $transformResult = static fn (TrinaryLogic $result) => $result->and(TrinaryLogic::createMaybe()); + $transformResult = static fn (IsSuperTypeOfResult $result) => $result->and(IsSuperTypeOfResult::createMaybe()); } } @@ -365,9 +370,9 @@ public function isSuperTypeOf(Type $type): TrinaryLogic $type instanceof SubtractableType && $type->getSubtractedType() !== null ) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); + $isSuperType = $type->getSubtractedType()->isSuperTypeOfWithReason($this); if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } } @@ -377,43 +382,43 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if ($thatClassNames[0] === $thisClassName) { - return $transformResult(TrinaryLogic::createYes()); + return $transformResult(IsSuperTypeOfResult::createYes()); } $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); $thisClassReflection = $this->getClassReflection(); if ($thisClassReflection === null || !$reflectionProvider->hasClass($thatClassNames[0])) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } $thatClassReflection = $reflectionProvider->getClass($thatClassNames[0]); if ($thisClassReflection->isTrait() || $thatClassReflection->isTrait()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($thisClassReflection->getName() === $thatClassReflection->getName()) { - return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes()); + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); } if ($thatClassReflection->isSubclassOf($thisClassName)) { - return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes()); + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); } if ($thisClassReflection->isSubclassOf($thatClassNames[0])) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 1144acd2f8..52dd0e7a21 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -69,38 +69,43 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof self) { if ($this->subtractedType === null) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type->subtractedType !== null) { - $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); + $isSuperType = $type->subtractedType->isSuperTypeOfWithReason($this->subtractedType); if ($isSuperType->yes()) { - return TrinaryLogic::createYes(); + return $isSuperType; } } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof ObjectShapeType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type->getObjectClassNames() === []) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($this->subtractedType === null) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $this->subtractedType->isSuperTypeOf($type)->negate(); + return $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); } public function equals(Type $type): bool diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 0be91db587..9cf4da4dc8 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -152,17 +152,22 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getStaticObjectType()->isSuperTypeOf($type); + return $this->getStaticObjectType()->isSuperTypeOfWithReason($type); } if ($type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof ObjectType) { - $result = $this->getStaticObjectType()->isSuperTypeOf($type); + $result = $this->getStaticObjectType()->isSuperTypeOfWithReason($type); if ($result->yes()) { $classReflection = $type->getClassReflection(); if ($classReflection !== null && $classReflection->isFinal()) { @@ -170,14 +175,14 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } } - return $result->and(TrinaryLogic::createMaybe()); + return $result->and(IsSuperTypeOfResult::createMaybe()); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 00a9141233..13b0fee7de 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -80,19 +80,29 @@ public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): public function isSuperTypeOf(Type $type): TrinaryLogic { - return TrinaryLogic::createYes(); + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + return IsSuperTypeOfResult::createYes(); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($otherType instanceof MixedType && !$otherType instanceof TemplateMixedType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } public function equals(Type $type): bool diff --git a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php index 0130d7e094..24a88696cb 100644 --- a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php +++ b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php @@ -9,25 +9,30 @@ class StringAlwaysAcceptingObjectWithToStringType extends StringType { public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } $thatClassNames = $type->getObjectClassNames(); if ($thatClassNames === []) { - return parent::isSuperTypeOf($type); + return parent::isSuperTypeOfWithReason($type); } - $result = TrinaryLogic::createNo(); + $result = IsSuperTypeOfResult::createNo(); $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); foreach ($thatClassNames as $thatClassName) { if (!$reflectionProvider->hasClass($thatClassName)) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } $typeClass = $reflectionProvider->getClass($thatClassName); - $result = $result->or(TrinaryLogic::createFromBoolean($typeClass->hasNativeMethod('__toString'))); + $result = $result->or(IsSuperTypeOfResult::createFromBoolean($typeClass->hasNativeMethod('__toString'))); } return $result; diff --git a/src/Type/ThisType.php b/src/Type/ThisType.php index 963f98c191..3eb3eb845a 100644 --- a/src/Type/ThisType.php +++ b/src/Type/ThisType.php @@ -35,18 +35,23 @@ public function describe(VerbosityLevel $level): string } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getStaticObjectType()->isSuperTypeOf($type); + return $this->getStaticObjectType()->isSuperTypeOfWithReason($type); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } $parent = new parent($this->getClassReflection(), $this->getSubtractedType()); - return $parent->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe()); + return $parent->isSuperTypeOfWithReason($type)->and(IsSuperTypeOfResult::createMaybe()); } public function changeSubtractedType(?Type $subtractedType): Type diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 527d10b68a..4c8c6a7294 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -10,6 +10,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\ConstantScalarType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\LooseComparisonHelper; use PHPStan\Type\Type; @@ -35,20 +36,25 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createFromBoolean($this->equals($type)); + return IsSuperTypeOfResult::createFromBoolean($this->equals($type)); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 3a90652de9..76855da1f4 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -14,6 +14,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\LateResolvableType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; @@ -59,24 +60,29 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { return $this->isSuperTypeOfDefault($type); } - private function isSuperTypeOfDefault(Type $type): TrinaryLogic + private function isSuperTypeOfDefault(Type $type): IsSuperTypeOfResult { if ($type instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof LateResolvableType) { $type = $type->resolve(); } - $isSuperType = $this->resolve()->isSuperTypeOf($type); + $isSuperType = $this->resolve()->isSuperTypeOfWithReason($type); if (!$this->isResolvable()) { - $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); + $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe()); } return $isSuperType; @@ -523,14 +529,19 @@ public function tryRemove(Type $typeToRemove): ?Type } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { $result = $this->resolve(); if ($result instanceof CompoundType) { - return $result->isSubTypeOf($otherType); + return $result->isSubTypeOfWithReason($otherType); } - return $otherType->isSuperTypeOf($result); + return $otherType->isSuperTypeOfWithReason($result); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic diff --git a/src/Type/Type.php b/src/Type/Type.php index b0ed8a7788..6c5064f4ea 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -78,6 +78,15 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult; public function isSuperTypeOf(Type $type): TrinaryLogic; + /** + * This is like isSuperTypeOf() but gives reasons + * why the type was not/might not be accepted in some non-intuitive scenarios. + * + * In PHPStan 2.0 this method will be removed and the return type of isSuperTypeOf() + * will change to IsSuperTypeOfResult. + */ + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult; + public function equals(Type $type): bool; public function describe(VerbosityLevel $level): string; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 0a3479d9a8..6b828625b6 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -205,6 +205,11 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($otherType)->result; + } + + public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ( ($otherType instanceof self && !$otherType instanceof TemplateUnionType) @@ -214,16 +219,16 @@ public function isSuperTypeOf(Type $otherType): TrinaryLogic || $otherType instanceof ConditionalTypeForParameter || $otherType instanceof IntegerRangeType ) { - return $otherType->isSubTypeOf($this); + return $otherType->isSubTypeOfWithReason($this); } - $result = TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType)); + $result = IsSuperTypeOfResult::createNo()->or(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOfWithReason($otherType), $this->types)); if ($result->yes()) { return $result; } if ($otherType instanceof TemplateUnionType) { - return $result->or($otherType->isSubTypeOf($this)); + return $result->or($otherType->isSubTypeOfWithReason($this)); } return $result; @@ -231,7 +236,12 @@ public function isSuperTypeOf(Type $otherType): TrinaryLogic public function isSubTypeOf(Type $otherType): TrinaryLogic { - return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType)); + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + { + return IsSuperTypeOfResult::extremeIdentity(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOfWithReason($innerType), $this->types)); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index add4ffca45..4353c6efdd 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -70,16 +70,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool