Open tolotrasamuel opened 12 months ago
You could use something that corresponds to this variant in order to proceed:
class Animal {
const Animal();
}
class Dog extends Animal {
const Dog();
void woof() => print('Woof!');
}
class Cat extends Animal {
const Cat();
void meow() => print('Meow!');
}
void main() {
final baz = Baz.values.byName("cat");
switch (baz) {
case Baz<Dog>():
baz.value.woof();
case Baz<Cat>():
baz.value.meow();
}
}
enum Baz<T extends Animal> {
dog(Dog()),
cat(Cat());
const Baz(this.value);
final T value;
}
In this particular case there is a 1-to-1 correspondence between the subtypes of Animal
and the values of Baz
, but in the case where this is not true you could perform a case analysis on the values when there is more than one value with the same actual type argument.
@johnniwinther, @stereotype441, would type inference be expected to handle the original example?
We generally do not attempt to infer types based on identical
comparisons, other than the null
check implicitly in == null
.
We could. Identical checks cannot be fooled, if the two values are identical, then they have the same runtime type, and we can promote either operand to the type of the other (if it's a subtype).
That is, if a check like identical(x1, x2)
is true, and the static type of x1
is a subtype of the static type of x2
, then we can promote the variable x2
to the type of x1
, or symmetrically promote x1
.
If the types are unrelated, nothing happens.
The best argument against is that nobody asked for a type check. We don't do general assignment promotion, we only promote to a "type of interest". But that's an assignment, identical
is a test, just not a type test, so it's more reasonable that it can promote. Also, if it had to be a type of interest, idential-promotion would likely never trigger, because you wouldn't do a type check before an identical check.
Then, to make this actually work, we'd have to special-case ==
checks on enums, maybe on other types which has "primitive equality", to be recognized as being equivalent to an identical check.
I'm OK with enums. Less OK with "primitive equality" in general, because that makes having primitive equality even more part of the public contract of a class. Currently that only matters for constants, so if your class doesn't have a const
constructor, you don't need to care about having primitive equality or not.
If having primitive equality is enough to let anyone do promotion based on ==
checks, then it becomes a much more prominent feature, and a breaking change to add a ==
operator, even if it behaves exactly as before.
So, no to that.
Which means that switch (variable) { case Animal.cat: ...
would promote only when variable
holds an enum type, since case constant
is a ==
check, not an identical check, and only if we choose to special-case enums (which are required to have primitive equality).
But being limited to enums also takes some of the usefulness away, because you can often just use the identical enum value instead of the variable:
witch (baz) {
case Baz.dog:
print(Baz.dog.value.woof()); // no error
break;
case Baz.cat:
Baz.cat.value.meow(); // no error
print('cat');
Baz.cat.value;
break;
}
Where that wouldn't work would be having multiple cases with the same type:
enum E<T> {
int<num>(1),
double<num>(1.0),
string<String>("");
T get value;
}
...
E<Object?> e = ...;
switch (e) {
case E.int:
case E.double:
print(e.value + 1);
case E.string:
print(e.value.length);
}
Here the promotion would make a difference.
Working only for enums is actually somewhat reasonable, since Null
is like an enum
, it has a fixed set of enumerated values (one, called null
). So it should probably also work for the last enum-like platform type, bool
.
So, if you do identical(v, e)
manually, then you can get promotion of v
to the static type of e
.
Which means someone will soon ask for an identical pattern, so they can get promotion in patterns too.
(Let's call it === c
:wink: )
If you do case c:
and c
has one of the types Null
, bool
or an enum type, then it means c == matchedValue
and will promote the same way as identical(c, matchedValue)
would.
If you do case == c:
and the matched value has a type that is one of Null
, bool
or an enum type, then you also get promotion like doing identical(matchedValue, c)
.
Could work.
You could use something that corresponds to this variant in order to proceed:
class Animal { const Animal(); } class Dog extends Animal { const Dog(); void woof() => print('Woof!'); } class Cat extends Animal { const Cat(); void meow() => print('Meow!'); } void main() { final baz = Baz.values.byName("cat"); switch (baz) { case Baz<Dog>(): baz.value.woof(); case Baz<Cat>(): baz.value.meow(); } } enum Baz<T extends Animal> { dog(Dog()), cat(Cat()); const Baz(this.value); final T value; }
In this particular case there is a 1-to-1 correspondence between the subtypes of
Animal
and the values ofBaz
, but in the case where this is not true you could perform a case analysis on the values when there is more than one value with the same actual type argument.@johnniwinther, @stereotype441, would type inference be expected to handle the original example?
This is nice, and it seems to works. The only issue is that I have to manually write the Generic type, and autofill does not do it by itself
in the switch case, it is clearly a cat or a dog, but Dart can't infer the type in the switch case. Is this a bug?
Dart can't infer the type in the switch case. Is this a bug?