-
Notifications
You must be signed in to change notification settings - Fork 668
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
Dog/Cat/Animal generic snafu should not be allowed #1603
Comments
Something like C#s |
That's great info, thanks! This is made a little more complicated/interesting with union types. Currently Psalm treats |
Since a mutable collection uses the generic type class ReadOnlyCollection<out T> {
get(int index): T
}
class WriteOnlyCollection<in T> {
set(int index, T value)
}
class Collection<T> {
set(int index, T value)
get(int index): T
}
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
let _: ReadOnlyCollection<Animal> = new ReadOnlyCollection<Dog>() // OK
let _: ReadOnlyCollection<Dog> = new ReadOnlyCollection<Animal>() // NOK
let _: ReadOnlyCollection<Dog|Cat> = new ReadOnlyCollection<Dog>() // OK
let _: ReadOnlyCollection<Dog> = new ReadOnlyCollection<Dog|Cat>() // NOK
let _: WriteOnlyCollection<Dog> = new WriteOnlyCollection<Animal>() // OK
let _: WriteOnlyCollection<Animal> = new WriteOnlyCollection<Dog>() // NOK
let _: WriteOnlyCollection<Dog> = new WriteOnlyCollection<Dog|Cat>() // OK
let _: WriteOnlyCollection<Dog|Cat> = new WriteOnlyCollection<Dog>() // NOK
let _: Collection<Animal> = new Collection<Dog>() // NOK
let _: Collection<Dog> = new Collection<Animal>() // NOK
let _: Collection<Dog|Cat> = new Collection<Dog>() // NOK
let _: Collection<Dog> = new Collection<Dog|Cat>() // NOK Hope this helps. |
Hmm but I also don't want an error with https://psalm.dev/r/2e0c889319 |
Unfortunately, this should be an error since |
You could allow casting |
Maybe this check could be bypassed when a |
Where |
Sounds a little inflexible. It's not just about mutability. |
Yeah, I guess I'd just rather have the So Sidenote: Hack has |
I have the implementation ready - it's just a matter of the circumstances under which this |
Have you considered this case? /**
* @template TKey
* @template KVal
* @psalm-template-allow-subtyping
*/
class Collection
{
/**
* @param TKey $key
* @return TVal
*/
public function get($key)
{
// ...
}
}
/** @var Collection<int, Dog> */
$dogCol = new Collection();
/** @var Collection<mixed, Animal> */
$animalCol = $dogCol;
$animalCol->get(new \stdClass()); How do you know which parameter is covariant/contravariant? On the other hand, this: /**
* @template TKey
* @template out KVal
*/
class Collection
{
/**
* @param TKey $key
* @return TVal
*/
public function get($key)
{
// ...
}
}
/** @var Collection<int, Dog> */
$dogCol = new Collection();
/** @var Collection<mixed, Animal> */ // <-- This is invalid
$animalCol = $dogCol;
$animalCol->get(new \stdClass()); would allow Psalm to know that this isn't allowed. |
Ok, you've convinced me it should be per-param - is |
@muglug Can't think of anything better. Are you also planning to error when https://dotnetfiddle.net/Jdi9mA One note: C# restricts |
I'm hesitant about Covariant sounds better - maybe something like
|
I see. Yeah that makes sense 👍 |
Something I forgot: There's an RFC for generics in PHP in the works. It looks like they do plan on using the |
Oh yeah, if/when that RFC's accepted I'll happily update the syntax! |
This is dope 👍 |
What should the message be, roughly? |
Something like that? |
/**
* @template T
*/
class Invariant
{
}
/**
* @template-covariant T
* @extends Invariant<T>
*/
class Covariant extends Invariant
{
} |
Input should be fine for a constructor, though? |
Why is that bad? Could you provide an example?
|
Yeah, constructors should be fine. Only public non-static methods methods should be relevant I think. I'll have to think more about casting in private methods. |
Hope this is understandable: https://psalm.dev/r/6d69fd7baf <?php
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
/**
* @template T
*/
class InvariantBox
{
/**
* @var T|null
*/
protected $animal;
/**
* @param T $value
*/
public function set($value): void
{
$this->animal = $value;
}
}
/**
* @template-covariant T
* @extends InvariantBox<T>
*/
class CovariantBox extends InvariantBox
{
}
/** @var CovariantBox<Dog> $dogBox */
$dogBox = new CovariantBox();
/** @var CovariantBox<Animal> $animalBox */
$animalBox = $dogBox;
$animalBox->set(new Cat()); Basically, we need to make sure that the inherited API also doesn't use |
Ah that makes sense - mind creating tickets for both, so I don't lose track? |
Done |
Thanks! |
Expected: InvalidArgument when passing
Collection<Dog>
toCollection<Animal>
Actual: No issue
The text was updated successfully, but these errors were encountered: