class A {
const B = [
1 => "January",
2 => "February",
3 => "March",
4 => "April",
5 => "May",
6 => "June",
7 => "July",
8 => "August",
9 => "September",
10 => "October",
11 => "November",
12 => "December",
];
}
/** @psalm-param keys-of(A::B) $i */
function getStringMonth(int $i) : void {}
/** @psalm-param values-of(A::B) $s */
function getIntMonth(string $s) : void {}
getStringMonth(5);
getIntMonth("January");
getStringMonth(13); // fails
getIntMonth("Mortuary"); // also fails
will this work for template tags & __get() / __set() / __isset() / __unset() etc. ?
Nice. Can I suggest keyof/valueof instead of keysof/valuesof? 5 is _a key_ of the array, singular. E.g. TypeScript uses _keyof_.
might also be handy to have optionalof, i.e. to transform array{x:float, y:float, z:float} to array{x?:float, y?:float, z?:float}
Can I suggest keyof/valueof instead of keysof/valuesof
Yeah, but it needs a hyphen (so key-of, value-of) to avoid being mistaken for an object type
@SignpostMarv
will this work for template tags & __get() / __set() / __isset() / __unset() etc. ?
Yeah, this will work
/**
* @template DATA as array<string, scalar|array|object|null>
*/
abstract class DataBag {
/**
* @var DATA
*/
protected $data;
/**
* @param DATA $data
*/
public function __construct(array $data) {
$this->data = $data;
}
/**
* @param key-of<DATA> $property
*/
public function __get(string $property) {
return $this->data[$property] ?? null;
}
/**
* @param key-of<DATA> $property
*/
public function __set(string $property, $value) {
$this->data[$property] = $value;
}
}
BUT I don't know how you'd be able to specify the output of __get in such a way as to parameterise it on possible input values.
maybe @return value-of<DATA, $property>, though that looks _weird_
maybe
@return value-of<DATA, $property>, though that looks _weird_
@return DATA[$property] ?
Will this enforce __set($property, $value) to require specific $value, i.e.
/**
* @template-extends ParamBag<array{page:int, section:string}>
*/
class PagedContentRequest extends ParamBag {
}
$foo = new PagedContentRequest(['page' => 1, 'section' => 'a']);
$foo->page = '2'; // not an int
$foo->section = 2; // not a string
I'm also wondering how feasible/sensible it'd be to do this:
/**
* @template DATA as array<string, scalar|array|object>
*
* @template-extends ParamBag<DATA>
*/
abstract class PossiblyBadIdea extends ParamBag {
/**
* @param key-of<DATA> $property
*/
public function __isset(string $property) : bool {
return true; // because value-of can never be null
}
}
Maybe this syntax (roughly translated from TypeScript) might work:
/**
* @template T as array
* @template K as key-of<T>
* @param T $o
* @param K $name
* @return T[K]
*/
function getOffset(array $o, $name) {
return $o[$name];
}
There are still a few bugs, but progress: https://psalm.dev/r/e7ea73d8f6
Am I correct in thinking that where T is array{a: int, b: string}, T|array<empty, empty> would resolve to array{a?: int, b?: string} ?
Not sure atm...
Most helpful comment
Maybe this syntax (roughly translated from TypeScript) might work: