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
2 changes: 2 additions & 0 deletions src/Reflection/ClassConstantReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ public function getNativeType(): ?Type;

public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock;

public function getInitializerExprType(): Type;

}
5 changes: 5 additions & 0 deletions src/Reflection/Dummy/DummyClassConstantReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,9 @@ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
return null;
}

public function getInitializerExprType(): Type
{
return new MixedType();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/RealClassClassConstantReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ public function getValueType(): Type
return $this->valueType;
}

public function getInitializerExprType(): Type
{
return $this->initializerExprTypeResolver->getType($this->getValueExpr(), InitializerExprContext::fromClassReflection($this->declaringClass));
}

public function getDeclaringClass(): ClassReflection
{
return $this->declaringClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,9 @@ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
return $this->constantReflection->getResolvedPhpDoc();
}

public function getInitializerExprType(): Type
{
return $this->constantReflection->getInitializerExprType();
}

}
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.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());

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());
}

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.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(); }

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(); }
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->getInitializerExprType();
}

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));
}

}
91 changes: 87 additions & 4 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 All @@ -173,5 +173,88 @@ public function test(): string

function testFinalTypedConstant(WithFinalTypedConstant $foo): void
{
assertType('non-empty-string', $foo->test());
assertType("'foo'", $foo->test());
}

final class FinalClassWithNativeType
{
const string FOO_BAR = 'foo';

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

function testFinalClassWithNativeType(FinalClassWithNativeType $foo): void
{
assertType("'foo'", $foo->test());
}

final class FinalClassWithPhpDocType
{
/** @var non-empty-string */
const FOO_BAR = 'foo';

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

function testFinalClassWithPhpDocType(FinalClassWithPhpDocType $foo): void
{
assertType("'foo'", $foo->test());
}

final class FinalClassWithBothTypes
{
/** @var non-empty-string */
const string FOO_BAR = 'foo';

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

function testFinalClassWithBothTypes(FinalClassWithBothTypes $foo): void
{
assertType("'foo'", $foo->test());
}

class WithFinalPhpDocConstant
{
/** @var non-empty-string */
final const FOO_BAR = 'foo';

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

function testFinalPhpDocConstant(WithFinalPhpDocConstant $foo): void
{
assertType("'foo'", $foo->test());
}

class WithFinalNativeConstant
{
final const string FOO_BAR = 'foo';

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

function testFinalNativeConstant(WithFinalNativeConstant $foo): void
{
assertType("'foo'", $foo->test());
}
99 changes: 99 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-14556.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?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());
}

class WithNativeType
{
const string FOO_BAR = 'foo';

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

function testNativeType(WithNativeType $foo): void
{
assertType('string', $foo->test());
}

class WithPhpDocType
{
/** @var non-empty-string */
const FOO_BAR = 'foo';

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

function testPhpDocType(WithPhpDocType $foo): void
{
assertType('non-empty-string', $foo->test());
}

class WithFinalConstant
{
final const FOO_BAR = 'foo';

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

function testFinalConstant(WithFinalConstant $foo): void
{
assertType("'foo'", $foo->test());
}

class WithFinalTypedConstant
{
/** @var non-empty-string */
final const string FOO_BAR = 'foo';

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

function testFinalTypedConstant(WithFinalTypedConstant $foo): void
{
assertType("'foo'", $foo->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