-
Notifications
You must be signed in to change notification settings - Fork 667
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
Lacking support for key-of<properties-of<T>>
#9367
Comments
Hey @boesing, can you reproduce the issue on https://psalm.dev ? |
Cool feature! On top of what is mentioned above, I think something like this is also missing for collecting the value type. class Test
{
/**
* @template T of object
* @template P of properties-of<T>
*
* @param T $object
* @param key-of<P> $name
*
* @return T[P]
*/
public static function get(object $object, string $name): mixed
{
}
}
class Data
{
public string $foo = 'foo!';
public int $bar = 42;
}
$data = new Data();
Test::get($data, 'foo'); // string
Test::get($data, 'aa'); // int In the exmple you can see the return type of:
This is in line with what is acceptable on arrays already: Not sure if that syntax makes a lot of sense for objects, but it's also how it works in typescript so it might make sense: const x = {firstName: 'a', lastName: 1};
function get<X extends object, K extends keyof X>(object: X, key : K): X[K] {
return object[key];
}
const a : string = get(x, 'firstName');
const b : number = get(x, 'lastName'); Maybe it's not very relevant, but I tried creating a psalm plugin for the example it looks like this: function property_get(object $object, string $name): mixed {
$classInfo = new \ReflectionClass($object);
$propInfo = $classInfo->getProperty($name);
return $propInfo->getValue($object);
} use PhpParser\Node\Arg;
use Psalm\Internal\Codebase\Reflection;
use Psalm\Plugin\ArgTypeInferer;
use Psalm\Plugin\DynamicFunctionStorage;
use Psalm\Plugin\EventHandler\DynamicFunctionStorageProviderInterface;
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent;
use Psalm\Storage\FunctionLikeParameter;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union;
class PropertyGetProvider implements DynamicFunctionStorageProviderInterface
{
/**
* @return array<lowercase-string>
*/
public static function getFunctionIds(): array
{
return ['property_get'];
}
public static function getFunctionStorage(DynamicFunctionStorageProviderEvent $event): ?DynamicFunctionStorage
{
$args = $event->getArgs();
$inferrer = $event->getArgTypeInferer();
$objectType = self::inferObjectType($inferrer, $args[0]);
if (!$objectType) {
return null;
}
$propertyNameType = self::inferPropertyNameType($inferrer, $args[1]);
if (!$propertyNameType) {
return null;
}
try {
$rc = new \ReflectionClass($objectType->value);
$prop = $rc->getProperty($propertyNameType->value);
} catch (\ReflectionException) {
return null;
}
$inferredReturnType = Reflection::getPsalmTypeFromReflectionType($prop->getType());
$storage = new DynamicFunctionStorage();
$storage->params = [
new FunctionLikeParameter('object', false, new Union([$objectType])),
new FunctionLikeParameter('name', false, new Union([$propertyNameType])),
];
$storage->return_type = $inferredReturnType;
return $storage;
}
private static function inferObjectType(ArgTypeInferer $inferer, Arg $arg): TNamedObject | TTemplateParam | null
{
$objectTypeUnion = $inferer->infer($arg);
if (!$objectTypeUnion->isSingle()) {
return null;
}
/** @var TNamedObject | TTemplateParam | null $objectType */
$objectType = $objectTypeUnion->getSingleAtomic();
if (!$objectType->isNamedObjectType()) {
return null;
}
return $objectType;
}
private static function inferPropertyNameType(ArgTypeInferer $inferer, Arg $arg): TLiteralString | null
{
$propertyNameTypeUnion = $inferer->infer($arg);
if (!$propertyNameTypeUnion->isSingleStringLiteral()) {
return null;
}
return $propertyNameTypeUnion->getSingleStringLiteral();
}
} Looking forward to getting more out of this feature (and maybe even getting it to work on objects with dynamic props :o )! :) |
I found these snippets: https://psalm.dev/r/e8e7cbbdd0<?php
$x = ['a' => 'a', 'b' => 123];
/**
* @template A of array
* @template K of key-of<A>
*
* @param A $array
* @param K $key
* @return A[K]
*/
function x(array $array, string $key): mixed
{
return $array[$key];
}
$a = x($x, 'a');
$b = x($x, 'b');
/** @psalm-trace $a, $b */
|
There is also this slight, but important variation, where K can be the union of multiple keys, so T[K] will have to be contravariantly or covariantly composed based on where it's being used: /**
* @template T extends object
* @template K extends key-of<properties-of<T>>
*
* @param T $object
* @param list<K> $key
* @param key-of<properties-of<T[K]>> $sub_key
* @return T[K]
*/ |
In #7359,
properties-of
feature was introduced.I'd love to use this and almost directly stumbled upon the use-case mentioned in the title.
After reading latest comments in #7359, the lack of supporting generics in combination with
properties-of
andkey-of
was already detected.I'd like to add this as a feature request for now, maybe either @Patrick-Remy, @aurimaskiekis or even myself might implement this at some point in time.
@Patrick-Remy thanks for implementing this feature, but I am having some trouble trying to use it. I have tried many different combinations but I really can't seem to find a way to make it work.
P.S. I am trying on
dev-master
branchFew examples:
Also
key-of<T>
doesn't look like works and just says no issues:Tried even providing array shape but no luck.
Originally posted by @aurimasniekis in #7359 (comment)
The text was updated successfully, but these errors were encountered: