Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/Type/ValueOfType.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\Traits\LateResolvableTypeTrait;
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
Expand Down Expand Up @@ -50,8 +51,17 @@
protected function getResult(): Type
{
if ($this->type->isEnum()->yes()) {
$enumCases = $this->type->getEnumCases();
if (
$enumCases === []
&& $this->type instanceof TemplateType
&& (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()

Check warning on line 58 in src/Type/ValueOfType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { if ($this->type->isEnum()->yes()) { $enumCases = $this->type->getEnumCases(); - if ($enumCases === [] && $this->type instanceof TemplateType && (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()) { + if ($enumCases === [] && $this->type instanceof TemplateType && !(new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->no()) { return new UnionType([new IntegerType(), new StringType()]); } $valueTypes = [];

Check warning on line 58 in src/Type/ValueOfType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ { if ($this->type->isEnum()->yes()) { $enumCases = $this->type->getEnumCases(); - if ($enumCases === [] && $this->type instanceof TemplateType && (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()) { + if ($enumCases === [] && $this->type instanceof TemplateType && $this->type->getBound()->isSuperTypeOf(new ObjectType('BackedEnum'))->yes()) { return new UnionType([new IntegerType(), new StringType()]); } $valueTypes = [];

Check warning on line 58 in src/Type/ValueOfType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.2, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { if ($this->type->isEnum()->yes()) { $enumCases = $this->type->getEnumCases(); - if ($enumCases === [] && $this->type instanceof TemplateType && (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()) { + if ($enumCases === [] && $this->type instanceof TemplateType && !(new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->no()) { return new UnionType([new IntegerType(), new StringType()]); } $valueTypes = [];

Check warning on line 58 in src/Type/ValueOfType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.2, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ { if ($this->type->isEnum()->yes()) { $enumCases = $this->type->getEnumCases(); - if ($enumCases === [] && $this->type instanceof TemplateType && (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()) { + if ($enumCases === [] && $this->type instanceof TemplateType && $this->type->getBound()->isSuperTypeOf(new ObjectType('BackedEnum'))->yes()) { return new UnionType([new IntegerType(), new StringType()]); } $valueTypes = [];

Check warning on line 58 in src/Type/ValueOfType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { if ($this->type->isEnum()->yes()) { $enumCases = $this->type->getEnumCases(); - if ($enumCases === [] && $this->type instanceof TemplateType && (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()) { + if ($enumCases === [] && $this->type instanceof TemplateType && !(new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->no()) { return new UnionType([new IntegerType(), new StringType()]); } $valueTypes = [];

Check warning on line 58 in src/Type/ValueOfType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ { if ($this->type->isEnum()->yes()) { $enumCases = $this->type->getEnumCases(); - if ($enumCases === [] && $this->type instanceof TemplateType && (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()) { + if ($enumCases === [] && $this->type instanceof TemplateType && $this->type->getBound()->isSuperTypeOf(new ObjectType('BackedEnum'))->yes()) { return new UnionType([new IntegerType(), new StringType()]); } $valueTypes = [];
) {
return new UnionType([new IntegerType(), new StringType()]);
}

$valueTypes = [];
foreach ($this->type->getEnumCases() as $enumCase) {
foreach ($enumCases as $enumCase) {
$valueType = $enumCase->getBackingValueType();
if ($valueType === null) {
continue;
Expand Down
46 changes: 46 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-13282.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php // lint >= 8.1

namespace Bug13283;

use function PHPStan\Testing\assertType;

enum Test: string
{
case NAME = 'name';
case VALUE = 'value';
}

/**
* @template T of \BackedEnum
* @param class-string<T> $enum
* @phpstan-assert null|value-of<T> $value
*/
function assertValue(mixed $value, string $enum): void
{
if (null === $value) {
return;
}

if (! is_int($value) && ! is_string($value)) {
throw new \Exception();
}

if (null === $enum::tryFrom($value)) {
throw new \Exception();
}
}

function getFromRequest(): mixed
{
return 'name';
}

$v = getFromRequest();

assertType('mixed', $v);

assertValue($v, Test::class);

assertType("'name'|'value'|null", $v);

$a = null !== $v ? Test::from($v) : null;
38 changes: 38 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-13782.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php // lint >= 8.1

namespace Bug13782;

use BackedEnum;
use function PHPStan\Testing\assertType;

enum IntEnum : int
{
case A = 1;
case B = 2;
}

class EnumMethods
{
/**
* @template TEnum of BackedEnum
* @param TEnum $enum
* @return value-of<TEnum>
*/
public static function getValue(BackedEnum $enum): int|string
{
return $enum->value;
}

/**
* @template TEnum of BackedEnum
* @param TEnum|null $enum
* @return ($enum is TEnum ? value-of<TEnum> : null)
*/
public static function getNullableValue(?BackedEnum $enum): int|string|null
{
return $enum === null ? null : $enum->value;
}
}

assertType("2", EnumMethods::getValue(IntEnum::B));
assertType("2", EnumMethods::getNullableValue(IntEnum::B));
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,12 @@ public function testBug13208(): void
$this->analyse([__DIR__ . '/data/bug-13208.php'], []);
}

#[RequiresPhp('>= 8.1')]
public function testBug13282(): void
{
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-13282.php'], []);
}

public function testBug11609(): void
{
$this->analyse([__DIR__ . '/data/bug-11609.php'], [
Expand Down
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ public function testBug12274(): void
]);
}

#[RequiresPhp('>= 8.1')]
public function testBug13638(): void
{
$this->checkNullables = true;
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/bug-13638.php'], []);
}

public function testBug13484(): void
{
$this->checkExplicitMixed = true;
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-13638.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php // lint >= 8.1

namespace Bug13638;

use BackedEnum;

/**
* @template T of BackedEnum
* @param ?value-of<T> $a
* @return ($a is null ? list<?value-of<T>> : list<value-of<T>>)
*/
function test1(int | string | null $a): array
{
return [$a];
}
10 changes: 10 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3666,6 +3666,16 @@ public function testBug3396(): void
$this->analyse([__DIR__ . '/data/bug-3396.php'], []);
}

#[RequiresPhp('>= 8.1')]
public function testBug12219(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/bug-12219.php'], []);
}

public function testBug13511(): void
{
$this->checkThisOnly = false;
Expand Down
54 changes: 54 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-12219.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php // lint >= 8.1

namespace Bug12219;

class Bug
{
/**
* @template T of \BackedEnum
* @param class-string<T> $class
* @param value-of<T> $value
*/
public function foo(string $class, mixed $value): void
{
}

/**
* @template Q of \BackedEnum
* @param class-string<Q> $class
* @param value-of<Q> $value
*/
public function bar(string $class, mixed $value): void
{
// Parameter #2 $value of static method Bug::foo() contains unresolvable type.
$this->foo($class, $value);
}
}

interface SuperBackedEnum extends \BackedEnum
{
public function customMethod(): void;
}

class Bug2
{
/**
* @template T of SuperBackedEnum
* @param class-string<T> $class
* @param value-of<T> $value
*/
public function foo(string $class, mixed $value): void
{
}

/**
* @template Q of SuperBackedEnum
* @param class-string<Q> $class
* @param value-of<Q> $value
*/
public function bar(string $class, mixed $value): void
{
// Parameter #2 $value of static method Bug::foo() contains unresolvable type.
$this->foo($class, $value);
}
}
Loading