Skip to content

Commit 6442485

Browse files
Add tests
1 parent c25366e commit 6442485

File tree

7 files changed

+225
-0
lines changed

7 files changed

+225
-0
lines changed

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,13 @@ public function testBug13566(): void
11951195
]);
11961196
}
11971197

1198+
#[RequiresPhp('>= 8.1')]
1199+
public function testBug14429(): void
1200+
{
1201+
$this->treatPhpDocTypesAsCertain = false;
1202+
$this->analyse([__DIR__ . '/data/bug-14429.php'], []);
1203+
}
1204+
11981205
public function testBug13799(): void
11991206
{
12001207
$this->treatPhpDocTypesAsCertain = true;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php // lint >= 8.1
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14429;
6+
7+
function throw_if(bool $condition, string $message): void
8+
{
9+
if ($condition) { throw new \Exception($message); }
10+
}
11+
12+
class Foo
13+
{
14+
/**
15+
* @param list<string> $tags
16+
* @param list<float> $scores
17+
* @param \ArrayObject<string, string> $stringMap
18+
* @param \ArrayObject<string, int> $intKeyMap
19+
*/
20+
public function __construct(
21+
public array $tags,
22+
public array $scores,
23+
public ?\ArrayObject $stringMap = null,
24+
public ?\ArrayObject $intKeyMap = null,
25+
) {
26+
foreach ($tags as $tagsItem) {
27+
throw_if(!is_string($tagsItem), 'tags item must be string');
28+
}
29+
foreach ($scores as $scoresItem) {
30+
throw_if(!is_int($scoresItem) && !is_float($scoresItem), 'scores item must be number');
31+
}
32+
if ($stringMap !== null) {
33+
foreach ($stringMap as $stringMapValue) {
34+
throw_if(!is_string($stringMapValue), 'stringMap value must be string');
35+
}
36+
}
37+
if ($intKeyMap !== null) {
38+
foreach ($intKeyMap as $intKeyMapValue) {
39+
throw_if(!is_int($intKeyMapValue), 'intKeyMap value must be int');
40+
}
41+
}
42+
}
43+
}

tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,19 @@ public function testBug12973(): void
404404
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12973.php'], []);
405405
}
406406

407+
public function testBug3028(): void
408+
{
409+
$this->checkNullables = true;
410+
$this->checkExplicitMixed = true;
411+
$this->analyse([__DIR__ . '/data/bug-3028.php'], [
412+
[
413+
'Function Bug3028\run() should return Bug3028\Format<Bug3028\Output> but returns Bug3028\Format<O of Bug3028\Output>.',
414+
50,
415+
'Template type O on class Bug3028\Format is not covariant. Learn more: <fg=cyan>https://phpstan.org/blog/whats-up-with-template-covariant</>',
416+
],
417+
]);
418+
}
419+
407420
public function testBug12397(): void
408421
{
409422
$this->checkNullables = true;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug3028;
4+
5+
interface Output { }
6+
7+
final class OutputImpl1 implements Output { }
8+
final class OutputImpl2 implements Output { }
9+
10+
/** @psalm-template O of Output */
11+
interface Format
12+
{
13+
/** @psalm-return O */
14+
public function output() : Output;
15+
16+
/** @psalm-param O $o */
17+
public function replace(Output $o) : void;
18+
}
19+
20+
/** @implements Format<OutputImpl1> */
21+
final class FormatImpl1 implements Format
22+
{
23+
public OutputImpl1 $o;
24+
25+
public function __construct() {
26+
$this->o = new OutputImpl1;
27+
}
28+
29+
public function output() : Output
30+
{
31+
return new OutputImpl1();
32+
}
33+
34+
/**
35+
* @param OutputImpl1 $o
36+
*/
37+
public function replace(Output $o) : void
38+
{
39+
$this->o = $o;
40+
}
41+
}
42+
43+
44+
/**
45+
* @psalm-template O of Output
46+
* @psalm-param Format<O> $outputFormat
47+
* @return Format<Output>
48+
*/
49+
function run(Format $outputFormat) : Format {
50+
return $outputFormat;
51+
}
52+
53+
$a = new FormatImpl1;
54+
run($a)->replace(new OutputImpl2);

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,17 @@ public function testBug4525(): void
10501050
$this->analyse([__DIR__ . '/data/bug-4525.php'], []);
10511051
}
10521052

1053+
public function testBug5298(): void
1054+
{
1055+
$this->analyse([__DIR__ . '/data/bug-5298.php'], [
1056+
[
1057+
'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>>.',
1058+
37,
1059+
'Template type TWorldProvider on class Bug5298\WorldProviderManagerEntry is not covariant. Learn more: <fg=cyan>https://phpstan.org/blog/whats-up-with-template-covariant</>',
1060+
],
1061+
]);
1062+
}
1063+
10531064
public function testBug10924(): void
10541065
{
10551066
$this->analyse([__DIR__ . '/../Methods/data/bug-10924.php'], []);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug5298;
4+
5+
interface WorldProvider{}
6+
7+
interface WritableWorldProvider extends WorldProvider{}
8+
9+
/**
10+
* @phpstan-template TWorldProvider of WorldProvider
11+
* @phpstan-type IsValid \Closure(string $path) : bool
12+
* @phpstan-type FromPath \Closure(string $path) : TWorldProvider
13+
*/
14+
class WorldProviderManagerEntry{
15+
/** @phpstan-param TWorldProvider $provider */
16+
public function acceptsTWorldProvider(WorldProvider $provider) : void{}
17+
}
18+
19+
20+
final class WorldProviderManager{
21+
/**
22+
* @var WorldProviderManagerEntry[]
23+
* @phpstan-var array<string, WorldProviderManagerEntry<WorldProvider>>
24+
*/
25+
protected $providers = [];
26+
27+
/**
28+
* @phpstan-template T of WorldProvider
29+
* @phpstan-param WorldProviderManagerEntry<T> $providerEntry
30+
*/
31+
public function addProvider(WorldProviderManagerEntry $providerEntry, string $name, bool $overwrite = false) : void{
32+
$name = strtolower($name);
33+
if(!$overwrite and isset($this->providers[$name])){
34+
throw new \InvalidArgumentException("Alias \"$name\" is already assigned");
35+
}
36+
37+
$this->providers[$name] = $providerEntry; //should be an error, T of WorldProvider is not invariant with WorldProvider
38+
\PHPStan\dumpType($providerEntry);
39+
\PHPStan\dumpType($this->providers);
40+
}
41+
42+
public function doSomething(string $name, WorldProvider $provider) : void{
43+
$this->providers[$name]->acceptsTWorldProvider($provider); //error, WorldProvider might not be a subclass of the template bound
44+
}
45+
}
46+
47+
$p = new WorldProviderManager();
48+
/** @phpstan-var WorldProviderManagerEntry<WritableWorldProvider> */
49+
$entry = new WorldProviderManagerEntry(); //acceptsTWorldProvider() doesn't accept WorldProvider
50+
$p->addProvider($entry, "test");
51+
$p->doSomething("test", new class implements WorldProvider{}); //bang

tests/PHPStan/Type/ObjectTypeTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131
use PHPStan\Type\Constant\ConstantStringType;
3232
use PHPStan\Type\Enum\EnumCaseObjectType;
3333
use PHPStan\Type\Generic\GenericObjectType;
34+
use PHPStan\Type\Generic\TemplateObjectType;
3435
use PHPStan\Type\Generic\TemplateTypeFactory;
36+
use PHPStan\Type\Generic\TemplateTypeParameterStrategy;
3537
use PHPStan\Type\Generic\TemplateTypeScope;
3638
use PHPStan\Type\Generic\TemplateTypeVariance;
3739
use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait;
@@ -745,4 +747,48 @@ public function testClassReflectionParentWithTemplateBound(): void
745747
$this->assertSame(Model::class, $tModlel->describe(VerbosityLevel::precise()));
746748
}
747749

750+
public static function dataEquals(): array
751+
{
752+
return [
753+
[
754+
new ObjectType('Foo'),
755+
new ObjectType('Foo'),
756+
true,
757+
],
758+
[
759+
new ObjectType('Foo'),
760+
new ObjectType('Bar'),
761+
false,
762+
],
763+
[
764+
new ObjectType('Foo'),
765+
new GenericObjectType('Foo', []),
766+
false,
767+
],
768+
[
769+
new ObjectType('Foo'),
770+
new TemplateObjectType(
771+
TemplateTypeScope::createWithAnonymousFunction(),
772+
new TemplateTypeParameterStrategy(),
773+
TemplateTypeVariance::createInvariant(),
774+
'T',
775+
new ObjectType('Foo'),
776+
null,
777+
),
778+
false,
779+
],
780+
];
781+
}
782+
783+
#[DataProvider('dataEquals')]
784+
public function testEquals(ObjectType $type, Type $otherType, bool $expectedResult): void
785+
{
786+
$actualResult = $type->equals($otherType);
787+
$this->assertSame(
788+
$expectedResult,
789+
$actualResult,
790+
sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())),
791+
);
792+
}
793+
748794
}

0 commit comments

Comments
 (0)