Skip to content

Commit

Permalink
Merge pull request #21 from veewee/add-psalm-unknown-property-issue
Browse files Browse the repository at this point in the history
Let psalm known assignments of unknown properties
  • Loading branch information
veewee authored Jan 24, 2025
2 parents 3f78784 + 8a8880d commit 9db448e
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 7 deletions.
9 changes: 4 additions & 5 deletions src/Psalm/Reflect/Infer/PropertyValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

final class PropertyValueType
{
/**
* @throws UnreflectableException
*/
public static function infer(
TNamedObject | TTemplateParam | null $objectType,
TLiteralString | null $propertyNameType
Expand All @@ -22,11 +25,7 @@ public static function infer(
return null;
}

try {
$prop = ReflectedClass::fromFullyQualifiedClassName($objectType->value)->property($propertyNameType->value);
} catch (UnreflectableException $e) {
return null;
}
$prop = ReflectedClass::fromFullyQualifiedClassName($objectType->value)->property($propertyNameType->value);

return Reflection::getPsalmTypeFromReflectionType($prop->apply(
static fn (ReflectionProperty $reflected) => $reflected->getType()
Expand Down
19 changes: 18 additions & 1 deletion src/Psalm/Reflect/Provider/PropertyGetProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

namespace VeeWee\Reflecta\Psalm\Reflect\Provider;

use Psalm\Issue\UndefinedPropertyFetch;
use Psalm\IssueBuffer;
use Psalm\Plugin\DynamicFunctionStorage;
use Psalm\Plugin\EventHandler\DynamicFunctionStorageProviderInterface;
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent;
Expand All @@ -11,6 +13,7 @@
use VeeWee\Reflecta\Psalm\Reflect\Infer\ObjectType;
use VeeWee\Reflecta\Psalm\Reflect\Infer\PropertyNameType;
use VeeWee\Reflecta\Psalm\Reflect\Infer\PropertyValueType;
use VeeWee\Reflecta\Reflect\Exception\UnreflectableException;

final class PropertyGetProvider implements DynamicFunctionStorageProviderInterface
{
Expand All @@ -29,7 +32,21 @@ public static function getFunctionStorage(DynamicFunctionStorageProviderEvent $e

$objectType = ObjectType::infer($inferrer, $args[0]);
$propertyNameType = PropertyNameType::infer($inferrer, $args[1]);
$inferredReturnType = PropertyValueType::infer($objectType, $propertyNameType);

try {
$inferredReturnType = PropertyValueType::infer($objectType, $propertyNameType);
} catch (UnreflectableException $e) {
IssueBuffer::maybeAdd(
new UndefinedPropertyFetch(
$e->getMessage(),
$event->getCodeLocation(),
$objectType->value . '::' . $propertyNameType->value,
),
$event->getStatementSource()->getSuppressedIssues()
);

return null;
}

if (!$objectType || !$propertyNameType || !$inferredReturnType) {
return null;
Expand Down
19 changes: 18 additions & 1 deletion src/Psalm/Reflect/Provider/PropertySetProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

namespace VeeWee\Reflecta\Psalm\Reflect\Provider;

use Psalm\Issue\UndefinedPropertyAssignment;
use Psalm\IssueBuffer;
use Psalm\Plugin\DynamicFunctionStorage;
use Psalm\Plugin\EventHandler\DynamicFunctionStorageProviderInterface;
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent;
Expand All @@ -11,6 +13,7 @@
use VeeWee\Reflecta\Psalm\Reflect\Infer\ObjectType;
use VeeWee\Reflecta\Psalm\Reflect\Infer\PropertyNameType;
use VeeWee\Reflecta\Psalm\Reflect\Infer\PropertyValueType;
use VeeWee\Reflecta\Reflect\Exception\UnreflectableException;

final class PropertySetProvider implements DynamicFunctionStorageProviderInterface
{
Expand All @@ -29,7 +32,21 @@ public static function getFunctionStorage(DynamicFunctionStorageProviderEvent $e

$objectType = ObjectType::infer($inferrer, $args[0]);
$propertyNameType = PropertyNameType::infer($inferrer, $args[1]);
$inferredValueType = PropertyValueType::infer($objectType, $propertyNameType);

try {
$inferredValueType = PropertyValueType::infer($objectType, $propertyNameType);
} catch (UnreflectableException $e) {
IssueBuffer::maybeAdd(
new UndefinedPropertyAssignment(
$e->getMessage(),
$event->getCodeLocation(),
$objectType->value . '::' . $propertyNameType->value,
),
$event->getStatementSource()->getSuppressedIssues()
);

return null;
}

if (!$objectType || !$propertyNameType || !$inferredValueType) {
return null;
Expand Down
11 changes: 11 additions & 0 deletions tests/static-analyzer/Reflect/property_get.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,14 @@ function test_get_mixed_return_type_on_templated_object(): mixed

return $curried($z)($x);
}

/**
* @psalm-suppress UndefinedPropertyFetch
*/
function test_getting_unknown_property(): mixed
{
$unknown = 'unknown';
$x = new X();

return property_get($x, $unknown);
}
11 changes: 11 additions & 0 deletions tests/static-analyzer/Reflect/property_set.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ function test_set_invalid_prop_value_type(): X
return property_set($x, $z, 'nope');
}

/**
* @psalm-suppress UndefinedPropertyAssignment
*/
function test_assigning_unknown_property(): X
{
$unknown = 'unknown';
$x = new X();

return property_set($x, $unknown, 'nope');
}

function test_return_type_on_templated_object(): object
{
$curried = static fn (string $path): Closure => static fn (object $object, mixed $value): mixed => property_set($object, $path, $value);
Expand Down

0 comments on commit 9db448e

Please sign in to comment.