Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1473,12 +1473,6 @@ parameters:
count: 1
path: src/Type/ObjectShapeType.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Enum\EnumCaseObjectType is error-prone and deprecated. Use Type::getEnumCases() instead.'
identifier: phpstanApi.instanceofType
count: 1
path: src/Type/ObjectType.php

-
rawMessage: Doing instanceof PHPStan\Type\Generic\GenericObjectType is error-prone and deprecated.
identifier: phpstanApi.instanceofType
Expand All @@ -1488,7 +1482,7 @@ parameters:
-
rawMessage: 'Doing instanceof PHPStan\Type\ObjectType is error-prone and deprecated. Use Type::isObject() or Type::getObjectClassNames() instead.'
identifier: phpstanApi.instanceofType
count: 3
count: 2
path: src/Type/ObjectType.php

-
Expand Down
7 changes: 2 additions & 5 deletions src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
use function array_map;
use function array_values;
use function count;
use function get_class;
use function implode;
use function in_array;
use function sprintf;
Expand Down Expand Up @@ -624,11 +625,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult

public function equals(Type $type): bool
{
if (!$type instanceof self) {
return false;
}

if ($type instanceof EnumCaseObjectType) {
if (get_class($type) !== static::class) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,13 @@ public function testBug13566(): void
]);
}

#[RequiresPhp('>= 8.1')]
public function testBug14429(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-14429.php'], []);
}

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

declare(strict_types = 1);

namespace Bug14429;

function throw_if(bool $condition, string $message): void
{
if ($condition) { throw new \Exception($message); }
}

class Foo
{
/**
* @param list<string> $tags
* @param list<float> $scores
* @param \ArrayObject<string, string> $stringMap
* @param \ArrayObject<string, int> $intKeyMap
*/
public function __construct(
public array $tags,
public array $scores,
public ?\ArrayObject $stringMap = null,
public ?\ArrayObject $intKeyMap = null,
) {
foreach ($tags as $tagsItem) {
throw_if(!is_string($tagsItem), 'tags item must be string');
}
foreach ($scores as $scoresItem) {
throw_if(!is_int($scoresItem) && !is_float($scoresItem), 'scores item must be number');
}
if ($stringMap !== null) {
foreach ($stringMap as $stringMapValue) {
throw_if(!is_string($stringMapValue), 'stringMap value must be string');
}
}
if ($intKeyMap !== null) {
foreach ($intKeyMap as $intKeyMapValue) {
throw_if(!is_int($intKeyMapValue), 'intKeyMap value must be int');
}
}
}
}
13 changes: 13 additions & 0 deletions tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,19 @@ public function testBug12973(): void
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12973.php'], []);
}

public function testBug3028(): void
{
$this->checkNullables = true;
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/bug-3028.php'], [
[
'Function Bug3028\run() should return Bug3028\Format<Bug3028\Output> but returns Bug3028\Format<O of Bug3028\Output>.',
50,
'Template type O on class Bug3028\Format is not covariant. Learn more: <fg=cyan>https://phpstan.org/blog/whats-up-with-template-covariant</>',
],
]);
}

public function testBug12397(): void
{
$this->checkNullables = true;
Expand Down
54 changes: 54 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-3028.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php declare(strict_types = 1);

namespace Bug3028;

interface Output { }

final class OutputImpl1 implements Output { }
final class OutputImpl2 implements Output { }

/** @psalm-template O of Output */
interface Format
{
/** @psalm-return O */
public function output() : Output;

/** @psalm-param O $o */
public function replace(Output $o) : void;
}

/** @implements Format<OutputImpl1> */
final class FormatImpl1 implements Format
{
public OutputImpl1 $o;

public function __construct() {
$this->o = new OutputImpl1;
}

public function output() : Output
{
return new OutputImpl1();
}

/**
* @param OutputImpl1 $o
*/
public function replace(Output $o) : void
{
$this->o = $o;
}
}


/**
* @psalm-template O of Output
* @psalm-param Format<O> $outputFormat
* @return Format<Output>
*/
function run(Format $outputFormat) : Format {
return $outputFormat;
}

$a = new FormatImpl1;
run($a)->replace(new OutputImpl2);
16 changes: 16 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,22 @@ public function testBug12457(): void
]);
}

public function testGenericSubtype(): void
{
$this->checkTypeAgainstPhpDocType = true;
$this->strictWideningCheck = true;
$this->analyse([__DIR__ . '/data/generic-subtype.php'], [
[
'PHPDoc tag @var with type GenericSubtype\IRepository<E of GenericSubtype\IEntity> is not subtype of type GenericSubtype\IRepository<GenericSubtype\IEntity>.',
78,
],
[
'PHPDoc tag @var with type GenericSubtype\IRepository<GenericSubtype\Foo> is not subtype of type GenericSubtype\IRepository<GenericSubtype\IEntity>.',
131,
],
]);
}

public function testNewIsAlwaysFinalClass(): void
{
$this->checkTypeAgainstPhpDocType = true;
Expand Down
148 changes: 148 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/generic-subtype.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php // lint >= 8.0

namespace GenericSubtype;

interface IEntity {
/**
* @return IRepository<IEntity>
*/
public function getRepository(): IRepository;
}

interface IProperty {}

interface IPropertyContainer extends IProperty {}

/**
* @template E of IEntity
*/
interface IEntityAwareProperty extends IProperty {}

/**
* @template E of IEntity
* @extends IEntityAwareProperty<E>
*/
interface IRelationshipContainer extends IPropertyContainer, IEntityAwareProperty {}

interface IModel {
/**
* @template E of IEntity
* @template T of IRepository<E>
* @param class-string<T> $className
* @return T
*/
public function getRepository(string $className): IRepository;
}

/**
* @template E of IEntity
*/
interface IRepository {
public function getModel(): IModel;
}

class PropertyRelationshipMetadata {
/** @var class-string<IRepository<IEntity>> */
public string $repository;
}

/**
* @template E of IEntity
* @implements IRelationshipContainer<E>
*/
class HasOne implements IRelationshipContainer
{
/** @var E|null */
protected ?IEntity $parent = null;

/** @var IRepository<E>|null */
protected ?IRepository $targetRepository = null;

protected PropertyRelationshipMetadata $metadataRelationship;

/**
* @return E
*/
protected function getParentEntity(): IEntity
{
return $this->parent ?? throw new \InvalidArgumentException('Relationship is not attached to a parent entity.');
}

/**
* @return IRepository<E>
*/
protected function getTargetRepository(): IRepository
{
if ($this->targetRepository === null) {
/** @var IRepository<E> $targetRepository */
$targetRepository = $this->getParentEntity()
->getRepository()
->getModel()
->getRepository($this->metadataRelationship->repository);

$this->test($targetRepository);

$this->targetRepository = $targetRepository;
}

return $this->targetRepository;
}

/**
* @param IRepository<IEntity>
*/
protected function test(): void {}
}

class Foo implements IEntity {
public function getRepository(): IRepository {
throw new \BadMethodCallException();
}
}

/**
* @implements IRelationshipContainer<Foo>
*/
class HasOne2 implements IRelationshipContainer
{
/** @var Foo|null */
protected ?IEntity $parent = null;

/** @var IRepository<Foo>|null */
protected ?IRepository $targetRepository = null;

protected PropertyRelationshipMetadata $metadataRelationship;

/**
* @return Foo
*/
protected function getParentEntity(): IEntity
{
return $this->parent ?? throw new \InvalidArgumentException('Relationship is not attached to a parent entity.');
}

/**
* @return IRepository<Foo>
*/
protected function getTargetRepository(): IRepository
{
if ($this->targetRepository === null) {
/** @var IRepository<Foo> $targetRepository */
$targetRepository = $this->getParentEntity()
->getRepository()
->getModel()
->getRepository($this->metadataRelationship->repository);

$this->test($targetRepository);

$this->targetRepository = $targetRepository;
}

return $this->targetRepository;
}

/**
* @param IRepository<IEntity> $repository
*/
protected function test($repository): void {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,17 @@ public function testBug4525(): void
$this->analyse([__DIR__ . '/data/bug-4525.php'], []);
}

public function testBug5298(): void
{
$this->analyse([__DIR__ . '/data/bug-5298.php'], [
[
'Property Bug5298\WorldProviderManager::$providers (array<string, Bug5298\WorldProviderManagerEntry<Bug5298\WorldProvider>>) does not accept non-empty-array<string, Bug5298\WorldProviderManagerEntry<Bug5298\WorldProvider>|Bug5298\WorldProviderManagerEntry<T of Bug5298\WorldProvider>>.',
37,
'Template type TWorldProvider on class Bug5298\WorldProviderManagerEntry is not covariant. Learn more: <fg=cyan>https://phpstan.org/blog/whats-up-with-template-covariant</>',
],
]);
}

public function testBug10924(): void
{
$this->analyse([__DIR__ . '/../Methods/data/bug-10924.php'], []);
Expand Down
Loading
Loading