diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 1245743947..11d60685b5 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -36,6 +36,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; +use PHPStan\Type\Traits\SubstractableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use function get_class; use function sprintf; @@ -47,6 +48,7 @@ class MixedType implements CompoundType, SubtractableType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonGeneralizableTypeTrait; + use SubstractableTypeTrait; private ?Type $subtractedType; @@ -471,23 +473,9 @@ public function describe(VerbosityLevel $level): string return $level->handle( static fn (): string => 'mixed', static fn (): string => 'mixed', + fn (): string => 'mixed' . $this->describeSubtractedType($this->subtractedType, $level), function () use ($level): string { - $description = 'mixed'; - if ($this->subtractedType !== null) { - $description .= $this->subtractedType instanceof UnionType - ? sprintf('~(%s)', $this->subtractedType->describe($level)) - : sprintf('~%s', $this->subtractedType->describe($level)); - } - - return $description; - }, - function () use ($level): string { - $description = 'mixed'; - if ($this->subtractedType !== null) { - $description .= $this->subtractedType instanceof UnionType - ? sprintf('~(%s)', $this->subtractedType->describe($level)) - : sprintf('~%s', $this->subtractedType->describe($level)); - } + $description = 'mixed' . $this->describeSubtractedType($this->subtractedType, $level); if ($this->isExplicitMixed) { $description .= '=explicit'; diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 78e4473410..cdb01e7d31 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -46,6 +46,7 @@ use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; +use PHPStan\Type\Traits\SubstractableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use Stringable; use Throwable; @@ -68,6 +69,7 @@ class ObjectType implements TypeWithClassName, SubtractableType use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; use NonGeneralizableTypeTrait; + use SubstractableTypeTrait; private const EXTRA_OFFSET_CLASSES = [ 'DOMNamedNodeMap', // Only read and existence @@ -504,16 +506,7 @@ public function describe(VerbosityLevel $level): string return $reflectionProvider->getClassName($this->className); }; - $preciseWithSubtracted = function () use ($level): string { - $description = $this->className; - if ($this->subtractedType !== null) { - $description .= $this->subtractedType instanceof UnionType - ? sprintf('~(%s)', $this->subtractedType->describe($level)) - : sprintf('~%s', $this->subtractedType->describe($level)); - } - - return $description; - }; + $preciseWithSubtracted = fn (): string => $this->className . $this->describeSubtractedType($this->subtractedType, $level); return $level->handle( $preciseNameCallback, @@ -559,11 +552,7 @@ private function describeCache(): string $description .= '<' . implode(', ', $typeDescriptions) . '>'; } - if ($this->subtractedType !== null) { - $description .= $this->subtractedType instanceof UnionType - ? sprintf('~(%s)', $this->subtractedType->describe(VerbosityLevel::cache())) - : sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); - } + $description .= $this->describeSubtractedType($this->subtractedType, VerbosityLevel::cache()); $reflection = $this->classReflection; if ($reflection !== null) { diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index a3b47c7686..9823dcaf80 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -7,8 +7,8 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait; +use PHPStan\Type\Traits\SubstractableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; -use function sprintf; /** @api */ class ObjectWithoutClassType implements SubtractableType @@ -18,6 +18,7 @@ class ObjectWithoutClassType implements SubtractableType use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; use NonGeneralizableTypeTrait; + use SubstractableTypeTrait; private ?Type $subtractedType; @@ -120,16 +121,7 @@ public function describe(VerbosityLevel $level): string return $level->handle( static fn (): string => 'object', static fn (): string => 'object', - function () use ($level): string { - $description = 'object'; - if ($this->subtractedType !== null) { - $description .= $this->subtractedType instanceof UnionType - ? sprintf('~(%s)', $this->subtractedType->describe($level)) - : sprintf('~%s', $this->subtractedType->describe($level)); - } - - return $description; - }, + fn (): string => 'object' . $this->describeSubtractedType($this->subtractedType, $level), ); } diff --git a/src/Type/Traits/SubstractableTypeTrait.php b/src/Type/Traits/SubstractableTypeTrait.php new file mode 100644 index 0000000000..bf563eb899 --- /dev/null +++ b/src/Type/Traits/SubstractableTypeTrait.php @@ -0,0 +1,30 @@ +getSubtractedType() !== null) + ) { + return sprintf('~(%s)', $subtractedType->describe($level)); + } + + return sprintf('~%s', $subtractedType->describe($level)); + } + +} diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 103e88278a..780cb6e8c8 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -540,6 +540,18 @@ private static function unionWithSubtractedType( return $type; } + if ($subtractedType instanceof SubtractableType) { + $withoutSubtracted = $subtractedType->getTypeWithoutSubtractedType(); + if ($withoutSubtracted->isSuperTypeOf($type)->yes()) { + $subtractedSubtractedType = $subtractedType->getSubtractedType(); + if ($subtractedSubtractedType === null) { + return new NeverType(); + } + + return self::intersect($type, $subtractedSubtractedType); + } + } + if ($type instanceof SubtractableType) { $subtractedType = $type->getSubtractedType() === null ? $subtractedType diff --git a/tests/PHPStan/Analyser/nsrt/subtracted.php b/tests/PHPStan/Analyser/nsrt/subtracted.php new file mode 100644 index 0000000000..0bb82c342c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/subtracted.php @@ -0,0 +1,31 @@ +