From 6ba9ef29cb5ccd5b540815e899c95e6a1602ccad Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Sep 2021 13:35:53 +0200 Subject: [PATCH] Fixed checkExplicitMixed with TemplateMixedType --- src/Rules/RuleLevelHelper.php | 9 ++- src/Type/Generic/TemplateMixedType.php | 12 ++++ src/Type/Generic/TemplateStrictMixedType.php | 58 +++++++++++++++++++ src/Type/StrictMixedType.php | 8 +++ .../Rules/Functions/CallCallablesRuleTest.php | 16 ++++- .../PHPStan/Rules/Functions/data/bug-3566.php | 40 +++++++++++++ .../Methods/data/check-explicit-mixed.php | 22 +++++++ 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/Type/Generic/TemplateStrictMixedType.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-3566.php diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 151da55f84..4a119f416a 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -62,7 +62,10 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp if ( $this->checkExplicitMixed ) { - $acceptedType = TypeTraverser::map($acceptedType, static function (Type $type, callable $traverse): Type { + $traverse = static function (Type $type, callable $traverse): Type { + if ($type instanceof TemplateMixedType) { + return $type->toStrictMixedType(); + } if ( $type instanceof MixedType && $type->isExplicitMixed() @@ -71,7 +74,9 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp } return $traverse($type); - }); + }; + $acceptingType = TypeTraverser::map($acceptingType, $traverse); + $acceptedType = TypeTraverser::map($acceptedType, $traverse); } if ( diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 2c61b0885c..d661e2e77e 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -4,6 +4,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; +use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; /** @api */ @@ -60,4 +61,15 @@ public function traverse(callable $cb): Type return $this; } + public function toStrictMixedType(): TemplateStrictMixedType + { + return new TemplateStrictMixedType( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + new StrictMixedType() + ); + } + } diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php new file mode 100644 index 0000000000..0bda962a6f --- /dev/null +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -0,0 +1,58 @@ + */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + StrictMixedType $bound + ) + { + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic + { + return $this->isSuperTypeOf($type); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof MixedType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + +} diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index ab6b328ac1..90fbf8aebf 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -26,6 +26,14 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { + if ($type instanceof static) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + return TrinaryLogic::createNo(); } diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 7535f3b8c0..29bf8cddaa 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -14,9 +14,12 @@ class CallCallablesRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $checkExplicitMixed = false; + protected function getRule(): \PHPStan\Rules\Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed); return new CallCallablesRule( new FunctionCallParametersCheck( $ruleLevelHelper, @@ -166,4 +169,15 @@ public function testNamedArguments(): void ]); } + public function testBug3566(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-3566.php'], [ + [ + 'Parameter #1 $ of closure expects int, TMemberType given.', + 29, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3566.php b/tests/PHPStan/Rules/Functions/data/bug-3566.php new file mode 100644 index 0000000000..6a015f9bfc --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-3566.php @@ -0,0 +1,40 @@ + $array + * @phpstan-param \Closure(TMemberType) : void $validator + */ + public static function validateArrayValueType(array $array, \Closure $validator) : void{ + foreach($array as $k => $v){ + try{ + $validator($v); + }catch(\TypeError $e){ + throw new \TypeError("Incorrect type of element at \"$k\": " . $e->getMessage(), 0, $e); + } + } + } + + /** + * @phpstan-template TMemberType + * @phpstan-param TMemberType $t + * @phpstan-param \Closure(int) : void $validator + */ + public static function doFoo($t, \Closure $validator) : void{ + $validator($t); + } + + /** + * @phpstan-template TMemberType + * @phpstan-param TMemberType $t + * @phpstan-param \Closure(mixed) : void $validator + */ + public static function doFoo2($t, \Closure $validator) : void{ + $validator($t); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php index 7d50d87121..da65ff526b 100644 --- a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php +++ b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php @@ -67,3 +67,25 @@ public function doLorem($t): void } } + +class TemplateMixed +{ + + /** + * @template T + * @param T $t + */ + public function doFoo($t): void + { + $this->doBar($t); + } + + /** + * @param mixed $mixed + */ + public function doBar($mixed): void + { + $this->doFoo($mixed); + } + +}