Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support constant string/int as template bound #1402

Merged
merged 1 commit into from Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Rules/Generics/TemplateTypeCheck.php
Expand Up @@ -13,6 +13,8 @@
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FloatType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
Expand Down Expand Up @@ -101,7 +103,9 @@ public function check(
$boundTypeClass !== MixedType::class
&& $boundTypeClass !== ConstantArrayType::class
&& $boundTypeClass !== ArrayType::class
&& $boundTypeClass !== ConstantStringType::class
&& $boundTypeClass !== StringType::class
&& $boundTypeClass !== ConstantIntegerType::class
&& $boundTypeClass !== IntegerType::class
&& $boundTypeClass !== FloatType::class
&& $boundTypeClass !== BooleanType::class
Expand Down
54 changes: 54 additions & 0 deletions src/Type/Generic/TemplateConstantIntegerType.php
@@ -0,0 +1,54 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Generic;

use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
use PHPStan\Type\Type;

/** @api */
final class TemplateConstantIntegerType extends ConstantIntegerType implements TemplateType
{

/** @use TemplateTypeTrait<ConstantIntegerType> */
use TemplateTypeTrait;
use UndecidedComparisonCompoundTypeTrait;

public function __construct(
TemplateTypeScope $scope,
TemplateTypeStrategy $templateTypeStrategy,
TemplateTypeVariance $templateTypeVariance,
string $name,
ConstantIntegerType $bound,
)
{
parent::__construct($bound->getValue());
$this->scope = $scope;
$this->strategy = $templateTypeStrategy;
$this->variance = $templateTypeVariance;
$this->name = $name;
$this->bound = $bound;
}

public function traverse(callable $cb): Type
{
$newBound = $cb($this->getBound());
if ($this->getBound() !== $newBound && $newBound instanceof ConstantIntegerType) {
return new self(
$this->scope,
$this->strategy,
$this->variance,
$this->name,
$newBound,
);
}

return $this;
}

protected function shouldGeneralizeInferredType(): bool
{
return false;
}

}
54 changes: 54 additions & 0 deletions src/Type/Generic/TemplateConstantStringType.php
@@ -0,0 +1,54 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Generic;

use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
use PHPStan\Type\Type;

/** @api */
final class TemplateConstantStringType extends ConstantStringType implements TemplateType
{

/** @use TemplateTypeTrait<ConstantStringType> */
use TemplateTypeTrait;
use UndecidedComparisonCompoundTypeTrait;

public function __construct(
TemplateTypeScope $scope,
TemplateTypeStrategy $templateTypeStrategy,
TemplateTypeVariance $templateTypeVariance,
string $name,
ConstantStringType $bound,
)
{
parent::__construct($bound->getValue());
$this->scope = $scope;
$this->strategy = $templateTypeStrategy;
$this->variance = $templateTypeVariance;
$this->name = $name;
$this->bound = $bound;
}

public function traverse(callable $cb): Type
{
$newBound = $cb($this->getBound());
if ($this->getBound() !== $newBound && $newBound instanceof ConstantStringType) {
return new self(
$this->scope,
$this->strategy,
$this->variance,
$this->name,
$newBound,
);
}

return $this;
}

protected function shouldGeneralizeInferredType(): bool
{
return false;
}

}
10 changes: 10 additions & 0 deletions src/Type/Generic/TemplateTypeFactory.php
Expand Up @@ -7,6 +7,8 @@
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
Expand Down Expand Up @@ -55,10 +57,18 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
return new TemplateStringType($scope, $strategy, $variance, $name, $bound);
}

if ($bound instanceof ConstantStringType && ($boundClass === ConstantStringType::class || $bound instanceof TemplateType)) {
return new TemplateConstantStringType($scope, $strategy, $variance, $name, $bound);
}

if ($bound instanceof IntegerType && ($boundClass === IntegerType::class || $bound instanceof TemplateType)) {
return new TemplateIntegerType($scope, $strategy, $variance, $name, $bound);
}

if ($bound instanceof ConstantIntegerType && ($boundClass === ConstantIntegerType::class || $bound instanceof TemplateType)) {
return new TemplateConstantIntegerType($scope, $strategy, $variance, $name, $bound);
}

if ($bound instanceof FloatType && ($boundClass === FloatType::class || $bound instanceof TemplateType)) {
return new TemplateFloatType($scope, $strategy, $variance, $name, $bound);
}
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Expand Up @@ -841,6 +841,12 @@ public function testBug7351(): void
$this->assertNoErrors($errors);
}

public function testBug7381(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7381.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -905,6 +905,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string-strcasing-specifying.php');
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/conditional-complex-templates.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7374.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/template-constant-bound.php');
}

/**
Expand Down
47 changes: 47 additions & 0 deletions tests/PHPStan/Analyser/data/bug-7381.php
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace Bug7381;

/**
* @template T of array<string, mixed>
*/
trait AttributeTrait
{
/**
* @template K of key-of<T>
* @param K $key
* @return T[K]|null
*/
public function getAttribute(string $key)
{
return $this->getAttributes()[$key] ?? null;
}
}

/**
* @phpstan-type Attrs array{foo?: string}
*/
class Foo {
/** @use AttributeTrait<Attrs> */
use AttributeTrait;

/** @return Attrs */
public function getAttributes(): array
{
return [];
}
}

/**
* @phpstan-type Attrs array{foo?: string, bar?: string}
*/
class Bar {
/** @use AttributeTrait<Attrs> */
use AttributeTrait;

/** @return Attrs */
public function getAttributes(): array
{
return [];
}
}
17 changes: 17 additions & 0 deletions tests/PHPStan/Analyser/data/template-constant-bound.php
@@ -0,0 +1,17 @@
<?php declare(strict_types = 1);

namespace TemplateConstantBound;

use function PHPStan\Testing\assertType;

/**
* @template T1 of 'foo'
* @template T2 of 5
* @param T1 $foo
* @param T2 $bar
*/
function foo(string $foo, int $bar): void
{
assertType("T1 of 'foo' (function TemplateConstantBound\\foo(), argument)", $foo);
assertType('T2 of 5 (function TemplateConstantBound\foo(), argument)', $bar);
}
16 changes: 16 additions & 0 deletions tests/PHPStan/Rules/Generics/data/class-template.php
Expand Up @@ -79,3 +79,19 @@ class Dolor
{

};

/**
* @template T of 'string'
*/
class Sit
{

}

/**
* @template T of 5
*/
class Amet
{

}