-
Notifications
You must be signed in to change notification settings - Fork 523
Add stringable access check to ClassConstantRule #3910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
8e8bcd5
4e59d4f
f9d4a82
808ed2e
b0539eb
399e22d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ | |||||
use PhpParser\Node; | ||||||
use PhpParser\Node\Expr\BinaryOp\Identical; | ||||||
use PhpParser\Node\Expr\ClassConstFetch; | ||||||
use PhpParser\Node\Name; | ||||||
use PhpParser\Node\Scalar\String_; | ||||||
use PHPStan\Analyser\NullsafeOperatorHelper; | ||||||
use PHPStan\Analyser\Scope; | ||||||
|
@@ -42,6 +43,7 @@ public function __construct( | |||||
private RuleLevelHelper $ruleLevelHelper, | ||||||
private ClassNameCheck $classCheck, | ||||||
private PhpVersion $phpVersion, | ||||||
private bool $checkNonStringableDynamicAccess = true, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that we shouldn't use the default value, but I was getting the following error, probably due to a missing DI configuration, so I temporarily added
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My suggestion above will help you get rid of this error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After you |
||||||
) | ||||||
{ | ||||||
} | ||||||
|
@@ -63,6 +65,27 @@ public function processNode(Node $node, Scope $scope): array | |||||
$name = $constantString->getValue(); | ||||||
$constantNameScopes[$name] = $scope->filterByTruthyValue(new Identical($node->name, new String_($name))); | ||||||
} | ||||||
|
||||||
if ($this->checkNonStringableDynamicAccess) { | ||||||
$typeResult = $this->ruleLevelHelper->findTypeToCheck( | ||||||
$scope, | ||||||
$node->name, | ||||||
'', | ||||||
static fn (Type $type) => $type->isString()->yes(), | ||||||
); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After this call there should be: $type = $typeResult->getType();
if ($type instanceof ErrorType) {
return [];
} Some rules return "unknown class errors" but that's irrelevant here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dynamic fetching of constants doesn't do implicit casting, so it seems appropriate to simply check I was wrong in the early stages of this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rename the variable to |
||||||
|
||||||
$type = $typeResult->getType(); | ||||||
|
||||||
if (!$type->isString()->yes()) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All calls to findTypeToCheck follow this pattern. |
||||||
$className = $node->class instanceof Name | ||||||
? $scope->resolveName($node->class) | ||||||
: $scope->getType($node->class)->describe(VerbosityLevel::typeOnly()); | ||||||
|
||||||
$errors[] = RuleErrorBuilder::message(sprintf('Cannot fetch constant from %s with a non-stringable type %s.', $className, $nameType->describe(VerbosityLevel::precise()))) | ||||||
->identifier('classConstant.fetchInvalidExpression') | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message and the identifier should reflect we're trying to access a class constant by non-stringable name. That's not obvious. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it might be nice to mention the class name here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message is wrong regarding the isString/toString point you made above. This is no longer about "stringables", but about "strings". Also the identifier should reflect it's about "name". |
||||||
->build(); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
foreach ($constantNameScopes as $constantName => $constantScope) { | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php // lint >= 8.3 | ||
|
||
namespace ClassConstantDynamicStringableAccess; | ||
|
||
use Stringable; | ||
use DateTime; | ||
use DateTimeImmutable; | ||
|
||
abstract class Foo | ||
{ | ||
|
||
public function test(mixed $mixed, ?string $nullableStr, ?Stringable $nullableStringable, int $int, ?int $nullableInt, DateTime|string $datetimeOrStr, Stringable $stringable): void | ||
{ | ||
echo self::{$mixed}; | ||
echo self::{$nullableStr}; | ||
echo self::{$nullableStringable}; | ||
echo self::{$int}; | ||
echo self::{$nullableInt}; | ||
echo self::{$datetimeOrStr}; | ||
echo self::{1111}; | ||
echo self::{(string)$stringable}; | ||
echo self::{$stringable}; // Uncast Stringable objects will cause a runtime error | ||
} | ||
|
||
} | ||
|
||
final class Bar extends Foo | ||
{ | ||
|
||
public function test(mixed $mixed, ?string $nullableStr, ?Stringable $nullableStringable, int $int, ?int $nullableInt, DateTime|string $datetimeOrStr, Stringable $stringable): void | ||
{ | ||
echo parent::{$mixed}; | ||
echo self::{$mixed}; | ||
} | ||
|
||
public function testClassDynamic(DateTime|DateTimeImmutable $datetime, object $obj, mixed $mixed): void | ||
{ | ||
echo $datetime::{$mixed}; | ||
echo $obj::{$mixed}; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this added here? It's not needed at all, the rule has
#[RegisteredRule(level: 0)]
attribute.