-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from veewee/psalm-plugin-for-multiple-properti…
…es-actions Infer properties_get and properties_set settings.
- Loading branch information
Showing
8 changed files
with
302 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace VeeWee\Reflecta\Psalm\Reflect\Infer; | ||
|
||
use Psalm\Internal\Codebase\Reflection; | ||
use Psalm\Type; | ||
use Psalm\Type\Atomic\TKeyedArray; | ||
use Psalm\Type\Atomic\TNamedObject; | ||
use Psalm\Type\Atomic\TTemplateParam; | ||
use Psalm\Type\Union; | ||
use ReflectionProperty; | ||
use VeeWee\Reflecta\Reflect\Exception\UnreflectableException; | ||
use VeeWee\Reflecta\Reflect\Type\ReflectedClass; | ||
use VeeWee\Reflecta\Reflect\Type\ReflectedProperty; | ||
use function Psl\Dict\map; | ||
|
||
final class PropertiesValuesType | ||
{ | ||
/** | ||
* @throws UnreflectableException | ||
*/ | ||
public static function infer( | ||
TNamedObject | TTemplateParam | null $objectType, | ||
bool $partial = false, | ||
): Union|null { | ||
if (!$objectType) { | ||
return null; | ||
} | ||
|
||
$class = ReflectedClass::fromFullyQualifiedClassName($objectType->value); | ||
$properties = $class->properties(); | ||
|
||
return new Union([ | ||
new TKeyedArray( | ||
map( | ||
$properties, | ||
static fn (ReflectedProperty $prop) => Reflection::getPsalmTypeFromReflectionType($prop->apply( | ||
static fn (ReflectionProperty $reflected) => $reflected->getType() | ||
))->setPossiblyUndefined($partial), | ||
), | ||
fallback_params: $class->isDynamic() ? [Type::getArrayKey(), Type::getMixed()] : null, | ||
) | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace VeeWee\Reflecta\Psalm\Reflect\Provider; | ||
|
||
use Psalm\Plugin\DynamicFunctionStorage; | ||
use Psalm\Plugin\EventHandler\DynamicFunctionStorageProviderInterface; | ||
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent; | ||
use Psalm\Storage\FunctionLikeParameter; | ||
use Psalm\Type; | ||
use Psalm\Type\Union; | ||
use VeeWee\Reflecta\Psalm\Reflect\Infer\ObjectType; | ||
use VeeWee\Reflecta\Psalm\Reflect\Infer\PropertiesValuesType; | ||
use function array_key_exists; | ||
|
||
final class PropertiesGetProvider implements DynamicFunctionStorageProviderInterface | ||
{ | ||
/** | ||
* @return array<lowercase-string> | ||
*/ | ||
public static function getFunctionIds(): array | ||
{ | ||
return ['veewee\reflecta\reflect\properties_get']; | ||
} | ||
|
||
public static function getFunctionStorage(DynamicFunctionStorageProviderEvent $event): ?DynamicFunctionStorage | ||
{ | ||
$args = $event->getArgs(); | ||
$inferrer = $event->getArgTypeInferer(); | ||
|
||
$objectType = ObjectType::infer($inferrer, $args[0]); | ||
$hasPredicate = array_key_exists(1, $args); | ||
$predicateType = $hasPredicate ? $inferrer->infer($args[1]) : Type::getNull(); | ||
$valuesType = PropertiesValuesType::infer($objectType, partial: $hasPredicate); | ||
|
||
if (!$objectType || !$valuesType) { | ||
return null; | ||
} | ||
|
||
$storage = new DynamicFunctionStorage(); | ||
$storage->params = [ | ||
new FunctionLikeParameter('object', false, new Union([$objectType])), | ||
new FunctionLikeParameter('predicate', false, $predicateType), | ||
]; | ||
$storage->return_type = $valuesType; | ||
|
||
return $storage; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace VeeWee\Reflecta\Psalm\Reflect\Provider; | ||
|
||
use Psalm\Plugin\DynamicFunctionStorage; | ||
use Psalm\Plugin\EventHandler\DynamicFunctionStorageProviderInterface; | ||
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent; | ||
use Psalm\Storage\FunctionLikeParameter; | ||
use Psalm\Type; | ||
use Psalm\Type\Union; | ||
use VeeWee\Reflecta\Psalm\Reflect\Infer\ObjectType; | ||
use VeeWee\Reflecta\Psalm\Reflect\Infer\PropertiesValuesType; | ||
use function array_key_exists; | ||
|
||
final class PropertiesSetProvider implements DynamicFunctionStorageProviderInterface | ||
{ | ||
/** | ||
* @return array<lowercase-string> | ||
*/ | ||
public static function getFunctionIds(): array | ||
{ | ||
return ['veewee\reflecta\reflect\properties_set']; | ||
} | ||
|
||
public static function getFunctionStorage(DynamicFunctionStorageProviderEvent $event): ?DynamicFunctionStorage | ||
{ | ||
$args = $event->getArgs(); | ||
$inferrer = $event->getArgTypeInferer(); | ||
|
||
$objectType = ObjectType::infer($inferrer, $args[0]); | ||
$valuesType = PropertiesValuesType::infer($objectType, partial: true); | ||
$predicateType = array_key_exists(2, $args) ? $inferrer->infer($args[2]) : Type::getNull(); | ||
|
||
if (!$objectType || !$valuesType) { | ||
return null; | ||
} | ||
|
||
$storage = new DynamicFunctionStorage(); | ||
$storage->params = [ | ||
new FunctionLikeParameter('object', false, new Union([$objectType])), | ||
new FunctionLikeParameter('values', false, $valuesType), | ||
new FunctionLikeParameter('predicate', false, $predicateType), | ||
]; | ||
$storage->return_type = new Union([$objectType]); | ||
|
||
return $storage; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace VeeWee\Reflecta\TestFixtures; | ||
|
||
final class MultipleProperties | ||
{ | ||
public string $a; | ||
public string $b; | ||
public string $c; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace VeeWee\Reflecta\SaTests\Reflect; | ||
|
||
use Closure; | ||
use VeeWee\Reflecta\Reflect\Type\Visibility; | ||
use VeeWee\Reflecta\TestFixtures\Dynamic; | ||
use VeeWee\Reflecta\TestFixtures\MultipleProperties; | ||
use VeeWee\Reflecta\TestFixtures\X; | ||
use function VeeWee\Reflecta\Reflect\Predicate\property_visibility; | ||
use function VeeWee\Reflecta\Reflect\properties_get; | ||
|
||
/** | ||
* @return array{z: int|null} | ||
*/ | ||
function test_get_prop_return_type(): array | ||
{ | ||
$x = new X(); | ||
$x->z = 123; | ||
|
||
return properties_get($x); | ||
} | ||
|
||
/** | ||
* @return array{z ?: int|null} | ||
*/ | ||
function test_get_optional_prop_return_type(): array | ||
{ | ||
$x = new X(); | ||
$x->z = 123; | ||
|
||
return properties_get($x, property_visibility(Visibility::Private)); | ||
} | ||
|
||
/** | ||
* @return array{a: string, b: string, c: string} | ||
*/ | ||
function test_get_multi_props_return_type(): array | ||
{ | ||
$x = new MultipleProperties(); | ||
|
||
return properties_get($x); | ||
} | ||
|
||
/** | ||
* @return array{a ?: string, b ?: string, c ?: string} | ||
*/ | ||
function test_get_optional_multi_props_return_type(): array | ||
{ | ||
$x = new MultipleProperties(); | ||
|
||
return properties_get($x, property_visibility(Visibility::Private)); | ||
} | ||
|
||
/** | ||
* @return array{x: string, ...<array-key, mixed>} | ||
*/ | ||
function test_get_dynamic_props_return_type(): array | ||
{ | ||
$x = new Dynamic(); | ||
|
||
return properties_get($x); | ||
} | ||
|
||
/** | ||
* @return array{x ?: string, ...<array-key, mixed>} | ||
*/ | ||
function test_get_optional_dynamic_props_return_type(): array | ||
{ | ||
$x = new Dynamic(); | ||
|
||
return properties_get($x, property_visibility(Visibility::Private)); | ||
} | ||
|
||
function test_get_mixed_return_type_on_templated_object(): array | ||
{ | ||
$curried = static fn (): Closure => static fn (object $object): array => properties_get($object); | ||
$x = new X(); | ||
|
||
return $curried()($x); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace VeeWee\Reflecta\SaTests\Reflect; | ||
|
||
use VeeWee\Reflecta\Reflect\Type\Visibility; | ||
use VeeWee\Reflecta\TestFixtures\Dynamic; | ||
use VeeWee\Reflecta\TestFixtures\MultipleProperties; | ||
use VeeWee\Reflecta\TestFixtures\X; | ||
use function VeeWee\Reflecta\Reflect\Predicate\property_visibility; | ||
use function VeeWee\Reflecta\Reflect\properties_set; | ||
|
||
function test_set_valid_prop_value_type(): X | ||
{ | ||
$x = new X(); | ||
$x->z = 123; | ||
|
||
return properties_set($x, ['z' => 456]); | ||
} | ||
|
||
function test_set_valid_prop_value_type_with_predicate(): X | ||
{ | ||
$x = new X(); | ||
$x->z = 123; | ||
|
||
return properties_set($x, ['z' => 456], property_visibility(Visibility::Private)); | ||
} | ||
|
||
function test_set_partial_props(): MultipleProperties | ||
{ | ||
$x = new MultipleProperties(); | ||
$x->a = ''; | ||
$x->b = ''; | ||
|
||
return properties_set($x, ['c' => 'foo']); | ||
} | ||
|
||
/** | ||
* @psalm-suppress InvalidScalarArgument | ||
*/ | ||
function test_set_invalid_prop_value_type(): X | ||
{ | ||
$x = new X(); | ||
$x->z = 123; | ||
|
||
return properties_set($x, ['z' => 'nope']); | ||
} | ||
|
||
/** | ||
* @psalm-suppress InvalidArgument | ||
*/ | ||
function test_assigning_unknown_property(): X | ||
{ | ||
$x = new X(); | ||
|
||
return properties_set($x, ['unknown' => 'nope']); | ||
} | ||
|
||
function test_set_new_prop_on_dynamic_class(): Dynamic | ||
{ | ||
$x = new Dynamic(); | ||
$x->x = 'string'; | ||
|
||
return properties_set($x, ['foo' => 'bar']); | ||
} |