Skip to content

Commit

Permalink
Fixed checkExplicitMixed with TemplateMixedType
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Sep 8, 2021
1 parent b4f81db commit 6ba9ef2
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 3 deletions.
9 changes: 7 additions & 2 deletions src/Rules/RuleLevelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 (
Expand Down
12 changes: 12 additions & 0 deletions src/Type/Generic/TemplateMixedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPStan\TrinaryLogic;
use PHPStan\Type\MixedType;
use PHPStan\Type\StrictMixedType;
use PHPStan\Type\Type;

/** @api */
Expand Down Expand Up @@ -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()
);
}

}
58 changes: 58 additions & 0 deletions src/Type/Generic/TemplateStrictMixedType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Generic;

use PHPStan\TrinaryLogic;
use PHPStan\Type\MixedType;
use PHPStan\Type\StrictMixedType;
use PHPStan\Type\Type;

/** @api */
final class TemplateStrictMixedType extends StrictMixedType implements TemplateType
{

/** @use TemplateTypeTrait<StrictMixedType> */
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;
}

}
8 changes: 8 additions & 0 deletions src/Type/StrictMixedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
16 changes: 15 additions & 1 deletion tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
],
]);
}

}
40 changes: 40 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-3566.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Bug3566;

class HelloWorld
{

/**
* @phpstan-template TMemberType
* @phpstan-param array<mixed, TMemberType> $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);
}
}
22 changes: 22 additions & 0 deletions tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}

0 comments on commit 6ba9ef2

Please sign in to comment.