On Tue, Aug 20, 2024, at 14:08, Arnaud Le Blanc wrote: > Hi Rob, > > On Mon, Aug 19, 2024 at 7:51 PM Rob Landers <rob@bottled.codes> wrote: > > > > Invariance would make arrays very difficult to adopt, as a library can > > > not start type hinting generic arrays without breaking user code, and > > > users can not pass generic arrays to libraries until they start using > > > generic arrays type declarations. > > > > This seems like a strawman argument, to a degree. In other words, it seems > > like you could combine static arrays and fluid arrays to accomplish what > > you are seeking to do. In other words, use static arrays but allow casting > > to treat it as "fluid." > > > > In other words, simply cast to get your example to compile: > > > > function f(array<int> $a) {} > > function g(array $a) {} > > > > $a = (array<int>) [1]; // array unless cast > > > > f($a); // ok > > g((array)$a); // ok > > > > And the other way: > > > > function f(array<int> $a) {} > > function g(array $a) {} > > > > $a = [1]; > > > > f((array<int>)$a); // ok, type check done during cast > > g($a); // ok > > There is potential for breaking changes in both of your examples: > > If f() is a library function that used to be declared as `f(array > $a)`, then changing its declaration to `f(array<int> $a)` is a > breaking change in the Static Arrays flavour, as it would break > library users until they change their code to add casts.
I don't think we should be scared of breaking changes; php 9.0 is coming 🔜 anyway. You could also consider it as "an array might be array<T>, but an array<T> is always an array" > > Similarly, the following code would break (when calling g()) if h() > was changed to return an array<int>: > > function h(): array {} > function g(array $a); > > $a = h(); > g($a); > > Casting would allow users to pass generic arrays to libraries that > don't support generics yet, but that's expensive as it requires a > copy. Why does it require a copy? It should only require a copy if the contents are changed (CoW) and at that point, you can know what rules to apply based on the coerced/casted type. I'm doing a similar thing for the Literal Strings RFC, where it is a type that is also indistinguishable from a string until something happens to it and it is no longer a literal string. So passing a array<int> to a function that only accepts an array shouldn't matter. Once inside that function, all type-checking can be disabled for that array. One approach to that could be to just smack a "type-check strategy" function pointer on zvals, potentially, as that would give the most flexibility for casting, aliases, generics, etc. Don't get me started on the current type checking; it is a mess and inconsistent depending on what is doing the checking (constructor promoted props, properties, method args, function args). Then you can just copy the zval, change a function pointer, but point it to the same array (which will CoW) and change the strategy during casting. In other words, you could cheaply cast an array<int> to array<string> by (essentially) changing a couple of function pointers, but array<string> to array<int> would be expensive. So I imagine there would strategies for changing strategies... probably. I don't know, I literally just thought of this off the top of my head, so it probably needs more work. > > Best Regards, > Arnaud > — Rob