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
48 changes: 44 additions & 4 deletions src/Type/ClassConstantAccessType.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\Traits\LateResolvableTypeTrait;
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
use function count;

final class ClassConstantAccessType implements CompoundType, LateResolvableType
{
Expand Down Expand Up @@ -49,13 +50,52 @@
return !TypeUtils::containsTemplateType($this->type);
}

protected function getResult(): Type
public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
{
if ($this->type->hasConstant($this->constantName)->yes()) {
return $this->type->getConstant($this->constantName)->getValueType();
$valueType = $this->type->getConstant($this->constantName)->getValueType();
return $otherType->isSuperTypeOf($valueType);

Check warning on line 57 in src/Type/ClassConstantAccessType.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->hasConstant($this->constantName)->yes()) { $valueType = $this->type->getConstant($this->constantName)->getValueType(); - return $otherType->isSuperTypeOf($valueType); + return $valueType->isSuperTypeOf($otherType); } return $otherType->isSuperTypeOf($this->resolve());

Check warning on line 57 in src/Type/ClassConstantAccessType.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->hasConstant($this->constantName)->yes()) { $valueType = $this->type->getConstant($this->constantName)->getValueType(); - return $otherType->isSuperTypeOf($valueType); + return $valueType->isSuperTypeOf($otherType); } return $otherType->isSuperTypeOf($this->resolve());
}

return $otherType->isSuperTypeOf($this->resolve());
}

public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
{
if ($this->type->hasConstant($this->constantName)->yes()) {
$valueType = $this->type->getConstant($this->constantName)->getValueType();
return $acceptingType->accepts($valueType, $strictTypes);
}

$result = $this->resolve();

if ($result instanceof CompoundType) {
return $result->isAcceptedBy($acceptingType, $strictTypes);
}

return $acceptingType->accepts($result, $strictTypes);
}

protected function getResult(): Type
{
if (!$this->type->hasConstant($this->constantName)->yes()) {

Check warning on line 81 in src/Type/ClassConstantAccessType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ protected function getResult(): Type { - if (!$this->type->hasConstant($this->constantName)->yes()) { + if ($this->type->hasConstant($this->constantName)->no()) { return new ErrorType(); }

Check warning on line 81 in src/Type/ClassConstantAccessType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ protected function getResult(): Type { - if (!$this->type->hasConstant($this->constantName)->yes()) { + if ($this->type->hasConstant($this->constantName)->no()) { return new ErrorType(); }
return new ErrorType();
}

$constantReflection = $this->type->getConstant($this->constantName);

$classReflections = $this->type->getObjectClassReflections();
$isFinalClass = count($classReflections) === 1 && $classReflections[0]->isFinal();

if ($isFinalClass || $constantReflection->isFinal()) {
return $constantReflection->getValueType();
}

if (!$constantReflection->hasPhpDocType() && !$constantReflection->hasNativeType()) {
return new MixedType();
}

return new ErrorType();
return $constantReflection->getValueType();
}

/**
Expand Down Expand Up @@ -89,7 +129,7 @@

public function toPhpDocNode(): TypeNode
{
return new ConstTypeNode(new ConstFetchNode('static', $this->constantName));
return new ConstTypeNode(new ConstFetchNode((string) $this->type->toPhpDocNode(), $this->constantName));
}

}
6 changes: 3 additions & 3 deletions tests/PHPStan/Analyser/nsrt/bug-13828.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class BarBaz extends FooBar

function test(FooBar $foo, BarBaz $bar): void
{
assertType("'foo'", $foo->test());
assertType("'bar'", $bar->test());
assertType('mixed', $foo->test());
assertType('mixed', $bar->test());
}

final class FinalFoo
Expand Down Expand Up @@ -146,7 +146,7 @@ public function test(): string

function testUntypedConstant(WithUntypedConstant $foo): void
{
assertType("'foo'", $foo->test());
assertType('mixed', $foo->test());
}

final class FinalChild extends FooBar
Expand Down
33 changes: 33 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-14556.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Bug14556;

use function PHPStan\Testing\assertType;

class FooBar
{
const FOO_BAR = 'foo';

/** @return static::FOO_BAR */
public function test(): string
{
return static::FOO_BAR;
}
}

class BarBaz extends FooBar
{
const FOO_BAR = 'bar';
}

final class FinalBarBaz extends FooBar
{
const FOO_BAR = 'bar';
}

function test(FooBar $foo, BarBaz $bar, FinalBarBaz $baz): void
{
assertType('mixed', $foo->test());
assertType('mixed', $bar->test());
assertType("'bar'", $baz->test());
}
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/nsrt/bug-6989.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MyClass
*/
public function myMethod(array $items1, array $items2, array $items3): array
{
assertType('array{key: string}', $items1);
assertType('non-empty-array<string>', $items1);
assertType('array{key: string}', $items2);
assertType('array{key: string}', $items3);

Expand All @@ -40,7 +40,7 @@ class ParentClass extends MyClass
*/
public function myMethod2(array $items1, array $items2, array $items3, array $items4, array $items5): array
{
assertType('array{different_key: string}', $items1);
assertType('non-empty-array<string>', $items1);
assertType('array{different_key: string}', $items2);
assertType('array{key: string}', $items3);
assertType('array{different_key: string}', $items4);
Expand Down
12 changes: 12 additions & 0 deletions tests/PHPStan/Type/TypeToPhpDocNodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,18 @@ public static function dataToPhpDocNodeWithoutCheckingEquals(): iterable
new ConstantFloatType(-0.0),
'-0.0',
];

$reflectionProvider = self::createReflectionProvider();

yield [
new ClassConstantAccessType(new StaticType($reflectionProvider->getClass(stdClass::class)), 'FOO'),
'static::FOO',
];

yield [
new ClassConstantAccessType(new ObjectType('stdClass'), 'FOO'),
'stdClass::FOO',
];
}

#[DataProvider('dataToPhpDocNodeWithoutCheckingEquals')]
Expand Down
Loading