Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions build/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ parameters:
message: '#^Variable property access on T of PHPStan\\Rules\\RuleError\.$#'
path: ../src/Rules/RuleErrorBuilder.php
-
message: "#^Parameter \\#1 (?:\\$argument|\\$objectOrClass) of class ReflectionClass constructor expects class\\-string\\<PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig\\>\\|PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig, string given\\.$#"
message: "#^Parameter \\#1 (?:\\$argument|\\$objectOrClass) of class ReflectionClass constructor expects class\\-string\\<PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig\\>\\|PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig, 'PHPStan…' given\\.$#"
count: 1
path: ../src/Command/CommandHelper.php
-
message: "#^Parameter \\#1 (?:\\$argument|\\$objectOrClass) of class ReflectionClass constructor expects class\\-string\\<PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig\\>\\|PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig, string given\\.$#"
message: "#^Parameter \\#1 (?:\\$argument|\\$objectOrClass) of class ReflectionClass constructor expects class\\-string\\<PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig\\>\\|PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig, 'PHPStan…' given\\.$#"
count: 1
path: ../src/Diagnose/PHPStanDiagnoseExtension.php
- identifier: ternary.shortNotAllowed
Expand Down
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ parameters:
path: src/Rules/RuleLevelHelper.php

-
rawMessage: 'Call to function method_exists() with ''PHPUnit\\Framework\\TestCase'' and ''assertFileDoesNotEx…'' will always evaluate to true.'
rawMessage: 'Call to function method_exists() with class-string<PHPUnit\Framework\TestCase> and ''assertFileDoesNotEx…'' will always evaluate to true.'
identifier: function.alreadyNarrowedType
count: 1
path: src/Testing/LevelsTestCase.php
Expand Down
14 changes: 8 additions & 6 deletions src/Type/Constant/ConstantStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,16 @@ public function describe(VerbosityLevel $level): string
return $level->handle(
static fn (): string => 'string',
function (): string {
if ($this->isClassString) {
return 'class-string<' . $this->value . '>';
}

$value = $this->value;

if (!$this->isClassString) {
try {
$value = Strings::truncate($value, self::DESCRIBE_LIMIT);
} catch (RegexpException) {
$value = substr($value, 0, self::DESCRIBE_LIMIT) . "\u{2026}";
}
try {
$value = Strings::truncate($value, self::DESCRIBE_LIMIT);
} catch (RegexpException) {
$value = substr($value, 0, self::DESCRIBE_LIMIT) . "\u{2026}";
}

return self::export($value);
Expand Down
4 changes: 4 additions & 0 deletions src/Type/VerbosityLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc
$moreVerbose = true;
return $type;
}
if ($type->isClassString()->yes()) {
$moreVerbose = true;
return $type;
}
return $traverse($type);
};

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public function testBug7171(): void
{
$this->analyse([__DIR__ . '/data/bug-7171.php'], [
[
'Parameter $repositoryClass of attribute class Bug7171\Entity constructor expects class-string<Bug7171\EntityRepository<T of object>>|null, \'stdClass\' given.',
'Parameter $repositoryClass of attribute class Bug7171\Entity constructor expects class-string<Bug7171\EntityRepository<T of object>>|null, class-string<stdClass> given.',
66,
],
]);
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ public function testInstanceof(): void
$tipText,
],
[
'Instanceof between class-string<DateTimeInterface> and \'DateTimeInterface\' will always evaluate to false.',
'Instanceof between class-string<DateTimeInterface> and class-string<DateTimeInterface> will always evaluate to false.',
432,
$tipText,
],
[
'Instanceof between DateTimeInterface and \'DateTimeInterface\' will always evaluate to true.',
'Instanceof between DateTimeInterface and class-string<DateTimeInterface> will always evaluate to true.',
433,
$tipText,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public function testImpossibleCheckTypeFunctionCall(): void
582,
],
[
'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExists\' and \'testWithStringFirst…\' will always evaluate to true.',
'Call to function method_exists() with class-string<CheckTypeFunctionCall\\MethodExists> and \'testWithStringFirst…\' will always evaluate to true.',
596,
],
[
Expand Down Expand Up @@ -196,30 +196,30 @@ public function testImpossibleCheckTypeFunctionCall(): void
656,
],
[
'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.',
'Call to function method_exists() with class-string<CheckTypeFunctionCall\MethodExistsWithTrait> and \'method\' will always evaluate to true.',
659,
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
],
[
'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.',
'Call to function method_exists() with class-string<CheckTypeFunctionCall\MethodExistsWithTrait> and \'someAnother\' will always evaluate to true.',
662,
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
],
[
'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.',
'Call to function method_exists() with class-string<CheckTypeFunctionCall\MethodExistsWithTrait> and \'unknown\' will always evaluate to false.',
665,
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
],
[
'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.',
'Call to function method_exists() with class-string<CheckTypeFunctionCall\MethodExistsWithTrait> and \'method\' will always evaluate to true.',
668,
],
[
'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.',
'Call to function method_exists() with class-string<CheckTypeFunctionCall\MethodExistsWithTrait> and \'someAnother\' will always evaluate to true.',
671,
],
[
'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.',
'Call to function method_exists() with class-string<CheckTypeFunctionCall\MethodExistsWithTrait> and \'unknown\' will always evaluate to false.',
674,
],
[
Expand Down Expand Up @@ -391,11 +391,11 @@ public function testBug6305(): void
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-6305.php'], [
[
'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\A\' will always evaluate to true.',
'Call to function is_subclass_of() with Bug6305\B and class-string<Bug6305\A> will always evaluate to true.',
11,
],
[
'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\B\' will always evaluate to false.',
'Call to function is_subclass_of() with Bug6305\B and class-string<Bug6305\B> will always evaluate to false.',
14,
],
]);
Expand All @@ -412,11 +412,11 @@ public function testBug13713(): void
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-13713.php'], [
[
"Call to function is_subclass_of() with arguments Bug13713\\test, 'stdClass' and false will always evaluate to true.",
"Call to function is_subclass_of() with arguments Bug13713\\test, class-string<stdClass> and false will always evaluate to true.",
12,
],
[
"Call to function is_subclass_of() with arguments class-string<Bug13713\\test>, 'stdClass' and true will always evaluate to true.",
"Call to function is_subclass_of() with arguments class-string<Bug13713\\test>, class-string<stdClass> and true will always evaluate to true.",
25,
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
],
Expand Down Expand Up @@ -1004,7 +1004,7 @@ public function testBugPR3404(): void
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-pr-3404.php'], [
[
'Call to function is_a() with arguments BugPR3404\Location, \'BugPR3404\\\\Location\' and true will always evaluate to true.',
'Call to function is_a() with arguments BugPR3404\Location, class-string<BugPR3404\\Location> and true will always evaluate to true.',
21,
],
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ public function testBug12473(): void
$tip,
],*/
[
'Call to method ReflectionClass<Bug12473\\PictureUser>::isSubclassOf() with \'Bug12473\\\\PictureProduct\' will always evaluate to false.',
'Call to method ReflectionClass<Bug12473\\PictureUser>::isSubclassOf() with class-string<Bug12473\\PictureProduct> will always evaluate to false.',
49,
$tip,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,12 +571,12 @@ public function testBug3633(): void
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\HelloWorld\' will always evaluate to true.',
'Strict comparison using === between class-string<Bug3633\HelloWorld> and class-string<Bug3633\HelloWorld> will always evaluate to true.',
41,
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\OtherClass\' will always evaluate to false.',
'Strict comparison using === between class-string<Bug3633\HelloWorld> and class-string<Bug3633\OtherClass> will always evaluate to false.',
44,
],
[
Expand All @@ -585,12 +585,12 @@ public function testBug3633(): void
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.',
'Strict comparison using === between class-string<Bug3633\OtherClass> and class-string<Bug3633\HelloWorld> will always evaluate to false.',
71,
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\OtherClass\' will always evaluate to true.',
'Strict comparison using === between class-string<Bug3633\OtherClass> and class-string<Bug3633\OtherClass> will always evaluate to true.',
74,
$tipText,
],
Expand All @@ -605,27 +605,27 @@ public function testBug3633(): void
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.',
'Strict comparison using === between class-string<Bug3633\FinalClass> and class-string<Bug3633\FinalClass> will always evaluate to true.',
102,
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.',
'Strict comparison using === between class-string<Bug3633\FinalClass> and class-string<Bug3633\HelloWorld> will always evaluate to false.',
106,
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\OtherClass\' will always evaluate to false.',
'Strict comparison using === between class-string<Bug3633\FinalClass> and class-string<Bug3633\OtherClass> will always evaluate to false.',
109,
$tipText,
],
[
'Strict comparison using !== between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to false.',
'Strict comparison using !== between class-string<Bug3633\FinalClass> and class-string<Bug3633\FinalClass> will always evaluate to false.',
112,
$tipText,
],
[
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.',
'Strict comparison using === between class-string<Bug3633\FinalClass> and class-string<Bug3633\FinalClass> will always evaluate to true.',
115,
],
]);
Expand Down
8 changes: 4 additions & 4 deletions tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,22 @@ public function testRule(): void
176,
],
[
'Trying to invoke array{\'CallCallables\\\\CallableInForeach\', \'bar\'|\'foo\'} but it might not be a callable.',
'Trying to invoke array{class-string<CallCallables\\CallableInForeach>, \'bar\'|\'foo\'} but it might not be a callable.',
188,
],
[
'Trying to invoke array{\'CallCallables\\\\ConstantArrayUnionCallables\'|\'DateTimeImmutable\', \'doFoo\'} but it might not be a callable.',
'Trying to invoke array{class-string<CallCallables\\ConstantArrayUnionCallables>|class-string<DateTimeImmutable>, \'doFoo\'} but it might not be a callable.',
214,
],
[
'Trying to invoke array{\'CallCallables\\\ConstantArrayUnionCallables\', \'doBaz\'|\'doFoo\'} but it might not be a callable.',
'Trying to invoke array{class-string<CallCallables\\ConstantArrayUnionCallables>, \'doBaz\'|\'doFoo\'} but it might not be a callable.',
221,
],
];

if (PHP_VERSION_ID >= 80000) {
$errors[] = [
'Trying to invoke array{\'CallCallables\\\ConstantArrayUnionCallables\'|\'CallCallables\\\ConstantArrayUnionCallablesTest\', \'doBar\'|\'doFoo\'} but it\'s not a callable.',
'Trying to invoke array{class-string<CallCallables\\ConstantArrayUnionCallables>|class-string<CallCallables\\ConstantArrayUnionCallablesTest>, \'doBar\'|\'doFoo\'} but it\'s not a callable.',
229,
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1161,7 +1161,7 @@ public function testBug4413(): void
require_once __DIR__ . '/data/bug-4413.php';
$this->analyse([__DIR__ . '/data/bug-4413.php'], [
[
'Parameter #1 $date of function Bug4413\takesDate expects class-string<DateTime>, string given.',
'Parameter #1 $date of function Bug4413\takesDate expects class-string<DateTime>, class-string<stdClass> given.',
18,
],
]);
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ public function testCallMethods(): void
1461,
],
[
'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string<Exception>, string given.',
'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string<Exception>, class-string<Throwable> given.',
1490,
],
[
Expand Down Expand Up @@ -834,7 +834,7 @@ public function testCallMethodsOnThisOnly(): void
1379,
],
[
'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string<Exception>, string given.',
'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string<Exception>, class-string<Throwable> given.',
1490,
],
[
Expand Down
10 changes: 5 additions & 5 deletions tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,11 @@ public function testBug4550(): void
$this->checkThisOnly = false;
$this->analyse([__DIR__ . '/data/bug-4550.php'], [
[
'Parameter #1 $class of static method Bug4550\Test::valuesOf() expects class-string<Person>, string given.',
"Parameter #1 \$class of static method Bug4550\Test::valuesOf() expects class-string<Person>, 'Person' given.",
34,
],
[
'Parameter #1 $class of static method Bug4550\Test::valuesOf() expects class-string<Person>, string given.',
"Parameter #1 \$class of static method Bug4550\Test::valuesOf() expects class-string<Person>, 'Person' given.",
44,
],
]);
Expand All @@ -482,7 +482,7 @@ public function testBug1971Php8(): void
$this->checkThisOnly = false;
$this->analyse([__DIR__ . '/data/bug-1971.php'], [
[
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{\'Bug1971\\\HelloWorld\', \'sayHello\'} given.',
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string<Bug1971\HelloWorld>, \'sayHello\'} given.',
14,
],
[
Expand Down Expand Up @@ -577,9 +577,9 @@ public function testTemplateTypeInOneBranchOfConditional(): void
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/template-type-in-one-branch-of-conditional.php'], [
[
'Parameter #1 $params of static method TemplateTypeInOneBranchOfConditional\DriverManager::getConnection() expects array{wrapperClass?: class-string<TemplateTypeInOneBranchOfConditional\Connection>}, array{wrapperClass: \'stdClass\'} given.',
'Parameter #1 $params of static method TemplateTypeInOneBranchOfConditional\DriverManager::getConnection() expects array{wrapperClass?: class-string<TemplateTypeInOneBranchOfConditional\Connection>}, array{wrapperClass: class-string<stdClass>} given.',
27,
"Offset 'wrapperClass' (class-string<TemplateTypeInOneBranchOfConditional\Connection>) does not accept type string.",
"Offset 'wrapperClass' (class-string<TemplateTypeInOneBranchOfConditional\Connection>) does not accept type class-string<stdClass>.",
],
[
'Unable to resolve the template type T in call to static method TemplateTypeInOneBranchOfConditional\DriverManager::getConnection()',
Expand Down
14 changes: 14 additions & 0 deletions tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1331,4 +1331,18 @@ public function testBug11430(): void
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-11430.php'], []);
}

public function testBug14440(): void
{
$this->analyse([__DIR__ . '/data/bug-14440.php'], [
[
'Method Bug14440\ChildOne::getCounterpartClass() should return class-string<Bug14440\ChildOne> but returns class-string<Bug14440\ChildTwo>.',
18,
],
[
'Method Bug14440\ChildTwo::getCounterpartClass() should return class-string<Bug14440\ChildTwo> but returns class-string<Bug14440\ChildOne>.',
27,
],
]);
}

}
29 changes: 29 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-14440.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php declare(strict_types = 1);

namespace Bug14440;

interface I {}

abstract class A
{
/** @return class-string<static&I> */
abstract public static function getCounterpartClass(): string;
}

final class ChildOne extends A implements I
{
#[\Override]
public static function getCounterpartClass(): string
{
return ChildTwo::class;
}
}

final class ChildTwo extends A implements I
{
#[\Override]
public static function getCounterpartClass(): string
{
return ChildOne::class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ public function testAppendedArrayItemType(): void
20,
],
[
'Property AppendedArrayItem\Foo::$callables (array<callable(): mixed>) does not accept non-empty-array<array{\'AppendedArrayItem\\\\Foo\', \'classMethod\'}|(callable(): mixed)>.',
'Property AppendedArrayItem\Foo::$callables (array<callable(): mixed>) does not accept non-empty-array<array{class-string<AppendedArrayItem\\Foo>, \'classMethod\'}|(callable(): mixed)>.',
23,
],
[
Expand Down
Loading