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
12 changes: 12 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,12 @@ parameters:
count: 2
path: src/Type/Generic/TemplateMixedType.php

-
message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#'
identifier: phpstanApi.instanceofType
count: 3
path: src/Type/Generic/TemplateNullType.php

-
message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#'
identifier: phpstanApi.instanceofType
Expand Down Expand Up @@ -1335,6 +1341,12 @@ parameters:
count: 1
path: src/Type/Generic/TemplateTypeFactory.php

-
message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#'
identifier: phpstanApi.instanceofType
count: 1
path: src/Type/Generic/TemplateTypeFactory.php

-
message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#'
identifier: phpstanApi.instanceofType
Expand Down
2 changes: 2 additions & 0 deletions src/Rules/Generics/TemplateTypeCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use PHPStan\Type\IterableType;
use PHPStan\Type\KeyOfType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectShapeType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
Expand Down Expand Up @@ -132,6 +133,7 @@ public function check(
&& $boundTypeClass !== GenericObjectType::class
&& $boundTypeClass !== KeyOfType::class
&& $boundTypeClass !== IterableType::class
&& $boundTypeClass !== NullType::class
&& !$boundType instanceof UnionType
&& !$boundType instanceof IntersectionType
&& !$boundType instanceof TemplateType
Expand Down
37 changes: 37 additions & 0 deletions src/Type/Generic/TemplateNullType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Generic;

use PHPStan\Type\NullType;
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
use PHPStan\Type\Type;

final class TemplateNullType extends NullType implements TemplateType
{

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

/**
* @param non-empty-string $name
*/
public function __construct(
TemplateTypeScope $scope,
TemplateTypeStrategy $templateTypeStrategy,
TemplateTypeVariance $templateTypeVariance,
string $name,
NullType $bound,
?Type $default,
)
{
parent::__construct();
$this->scope = $scope;
$this->strategy = $templateTypeStrategy;
$this->variance = $templateTypeVariance;
$this->name = $name;
$this->bound = $bound;
$this->default = $default;
}

}
5 changes: 5 additions & 0 deletions src/Type/Generic/TemplateTypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\Type\IterableType;
use PHPStan\Type\KeyOfType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectShapeType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
Expand Down Expand Up @@ -112,6 +113,10 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
return new TemplateIterableType($scope, $strategy, $variance, $name, $bound, $default);
}

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

return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true), $default);
}

Expand Down
53 changes: 53 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-12894.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug12894;

use Closure;

/**
* @template TValue of object|null
*/
interface Dependency
{

/**
* @return TValue
*/
public function __invoke(): object|null;

}

interface DependencyResolver
{

/**
* @template V of object|null
* @template D of Dependency<V>
*
* @param D $dependency
*
* @return V
*/
public function resolve(Dependency $dependency): object|null;

}

class Resolver implements DependencyResolver
{
/**
* @var Closure(object|null): void
*/
protected Closure $run;

public function resolve(Dependency $dependency): object|null {
$resolved = $dependency();
\PHPStan\Testing\assertType('V of object|null (method Bug12894\DependencyResolver::resolve(), argument)', $resolved);
$result = is_object($resolved) ? 1 : 2;
\PHPStan\Testing\assertType('V of object (method Bug12894\DependencyResolver::resolve(), argument)|V of null (method Bug12894\DependencyResolver::resolve(), argument)', $resolved);
($this->run)($resolved);
return $resolved;
}

}
17 changes: 17 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-12989.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types = 1);

namespace Bug12989;

/**
* @template T of int|null
* @phpstan-param T $b
* @phpstan-return int|T
*/
function a(?int $b): ?int
{
if ($b === null) {
\PHPStan\Testing\assertType('T of null (function Bug12989\a(), argument)', $b);
return $b;
}
return $b;
}
7 changes: 7 additions & 0 deletions tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,4 +435,11 @@ public function testPropertyHooks(): void
]);
}


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

}
21 changes: 21 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-13048.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php // lint >= 8.1

namespace Bug13048;

enum IndexBy {
case A;
case B;
}

/**
* @template T of IndexBy|null
* @param T $indexBy
*/
function run(?IndexBy $indexBy = null): ?string
{
return match ($indexBy) {
IndexBy::A => 'by A',
IndexBy::B => 'by B',
null => null,
};
}
4 changes: 0 additions & 4 deletions tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ public function testRule(): void
'PHPDoc tag @template T for function FunctionTemplateType\resourceBound() with bound type resource is not supported.',
50,
],
[
'PHPDoc tag @template T for function FunctionTemplateType\nullNotSupported() with bound type null is not supported.',
68,
],
[
'Call-site variance of covariant int in generic type FunctionTemplateType\GenericCovariant<covariant int> in PHPDoc tag @template U is redundant, template type T of class FunctionTemplateType\GenericCovariant has the same variance.',
94,
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Generics/data/function-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function nakano()
}

/** @template T of null */
function nullNotSupported()
function nullSupported()
{

}
Expand Down
16 changes: 0 additions & 16 deletions tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,6 @@ public function testGenericCallables(): void
'PHPDoc tag @param for parameter $invalidBoundType template T of Closure<T of GenericCallablesIncompatible\Invalid>(T): T has invalid bound type GenericCallablesIncompatible\Invalid.',
25,
],
[
'PHPDoc tag @param for parameter $notSupported template T of Closure<T of null>(T): T with bound type null is not supported.',
32,
],
[
'PHPDoc tag @param for parameter $shadows template T of Closure<T of mixed>(T): T shadows @template T for function GenericCallablesIncompatible\testShadowFunction.',
40,
Expand Down Expand Up @@ -360,10 +356,6 @@ public function testGenericCallables(): void
'PHPDoc tag @return template T of Closure<T of GenericCallablesIncompatible\Invalid>(T): T has invalid bound type GenericCallablesIncompatible\Invalid.',
90,
],
[
'PHPDoc tag @return template T of Closure<T of null>(T): T with bound type null is not supported.',
97,
],
[
'PHPDoc tag @return template T of Closure<T of mixed>(T): T shadows @template T for function GenericCallablesIncompatible\testShadowFunctionReturn.',
105,
Expand All @@ -380,10 +372,6 @@ public function testGenericCallables(): void
'PHPDoc tag @param for parameter $invalidBoundType template T of Closure<T of GenericCallablesIncompatible\Invalid>(T): T has invalid bound type GenericCallablesIncompatible\Invalid.',
131,
],
[
'PHPDoc tag @param for parameter $notSupported template T of Closure<T of null>(T): T with bound type null is not supported.',
138,
],
[
'PHPDoc tag @return template of Closure<stdClass of mixed>(stdClass): stdClass cannot have existing class stdClass as its name.',
145,
Expand All @@ -396,10 +384,6 @@ public function testGenericCallables(): void
'PHPDoc tag @return template T of Closure<T of GenericCallablesIncompatible\Invalid>(T): T has invalid bound type GenericCallablesIncompatible\Invalid.',
159,
],
[
'PHPDoc tag @return template T of Closure<T of null>(T): T with bound type null is not supported.',
166,
],
[
'PHPDoc tag @param-out for parameter $existingClass template T of Closure<T of mixed>(T): T shadows @template T for function GenericCallablesIncompatible\shadowsParamOut.',
175,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,6 @@ public function testGenericCallables(): void
'PHPDoc tag @var template of callable<TypeAlias of mixed>(TypeAlias): TypeAlias cannot have existing type alias TypeAlias as its name.',
26,
],
[
'PHPDoc tag @var template TNull of callable<TNull of null>(TNull): TNull with bound type null is not supported.',
31,
],
[
'PHPDoc tag @var template TInvalid of callable<TInvalid of GenericCallableProperties\Invalid>(TInvalid): TInvalid has invalid bound type GenericCallableProperties\Invalid.',
36,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ function invalidBoundType(Closure $invalidBoundType): void
}

/**
* @param Closure<T of null>(T $val): T $notSupported
* @param Closure<T of null>(T $val): T $closure
*/
function notSupported(Closure $notSupported): void
function testNull(Closure $closure): void
{
}

Expand Down Expand Up @@ -94,7 +94,7 @@ function invalidBoundTypeReturn(): Closure
/**
* @return Closure<T of null>(T $val): T
*/
function notSupportedReturn(): Closure
function nullReturn(): Closure
{
}

Expand Down Expand Up @@ -133,9 +133,9 @@ public function invalidBoundType(Closure $invalidBoundType): void
}

/**
* @param Closure<T of null>(T $val): T $notSupported
* @param Closure<T of null>(T $val): T $closure
*/
public function notSupported(Closure $notSupported): void
public function nullType(Closure $closure): void
{
}

Expand Down Expand Up @@ -163,7 +163,7 @@ public function invalidBoundTypeReturn(): Closure
/**
* @return Closure<T of null>(T $val): T
*/
public function notSupportedReturn(): Closure
public function nullReturn(): Closure
{
}
}
Expand Down
Loading