-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
casting with as
doesn't work on List
s
#49661
Comments
That's because Now the question is: why is Try: final x = [1];
foo(x); or foo(<int>[1]); instead. Here you will get an instance of |
@mraleph why do other types behave differently then? void main() {
Object a = 1;
print(a.runtimeType); // int
a as int; // int
List<Object> b = [1,2];
print(b.runtimeType); // JSArray<Object>
b as List<int>; // error
} why does |
If
|
that's unrelated imo. that's class Foo<T> {
Foo(this._value);
final T _value;
T getValue() => _value;
}
void main() {
Object a = 1;
print(a.runtimeType); // int
a as int; // int
Foo<Object> b = Foo(1);
print(b.runtimeType); // Foo<Object>
b as Foo<int>; // error
}
what other languages do this? imo the current behavior is very confusing. i saw lots of stackoverflow threads where nobody understood the problem and were providing convoluted workarounds without explaining the issue. if |
They don't behave differently. There are two different things: there is type of the object itself (e.g. what class an object is an instance of) and then there is type arguments supplied to that type. Imagine you have a generic class: class Foo<T> {
// ...
} When you write code which uses this class you can choose to omit Type inference fills omitted type arguments, but it does not change the type of the object being constructed (e.g. if you write Now this explains why final Foo<Object> foo1 = Foo(1);
final Foo<int> foo2 = foo1 as Foo<int>;
final int v = foo2.value;
foo2.value = "wat?";
final int u = foo2.value; // huh? static type says it is okay, but runtime types don't match
I don't think the linked thread has anything to do with this particular behaviour. It's a slightly different issue with a lack of horizontal type inference. Back before 2.12 the code below: // @dart=2.9
class StreamBuilder<T> {
final Stream<T> stream;
final void Function(T) callback;
StreamBuilder({this.stream, this.callback}) {
print(callback.runtimeType);
}
}
void main() {
final foo = Stream<String>.fromIterable(["A", "B", "C"]);
StreamBuilder(stream: foo, callback: (val) {
print(val.length);
});
} The expectation is that
Meaning that Applying this to the code in question would mean that (The same code would fail to compile between 2.12 and 2.18 because the inference was changed to infer In general when trying to answer a question "why some type check fails" people should systematically inspect results of type inference (e.g. in IDE) and then the answer will become clear. |
thanks for writing this up. though i still think this behavior is strange when it comes to generics as it seems to limit the casting functionality by needlessly widening the type at runtime |
Ah, I forgot the cc to me on this issue. I do have a couple of comments on the topic, so here we go.
History is certainly one reason: It's a breaking change to do something other than what we've done so far; changes to type inference are rather subtle to detect; and they may cause run-time errors a long time after the situation where the behavior of the program changed slightly. So we need to tread carefully when we change anything in this area. We have some proposals about sound variance (such as dart-lang/language#524 and dart-lang/language#753), but they haven't (yet) been accepted into the language. The underlying soundness issue has been known at least since the late 1970'ies (of course, it's not that hard to discover), and they were very well-known when Dart was designed to have dynamically checked covariance back in 2010 or so (several years before I joined Google). So the choice to use dynamic checks rather than using some rules that are statically safe was deliberate. It's a trade-off between (1) simple and flexible rules, and (2) type safety. We get more of (1) by postponing the enforcement of (2) to run time. I've been profoundly surprised that we haven't had more pressure in the direction of switching to a ruleset which is fully statically checked, but it's really not a topic that comes up very often in practice. Note that dynamically checked covariance is well-known from mainstream languages: Both Java and C# use dynamically checked covariance for arrays. Anyway, this means that we can have the following situation: void main() {
List<num> xs = [1];
xs.add(1.5);
} In this situation, it would be perfectly OK, locally, to have a variable like So we could infer the type argument of the newly created list using a bottom-up computation (yielding However, what we really want is invariance, because newly created lists tend to be modified, and this means that we will encounter run-time errors if we use the permission to let the variable of type In particular, if the variable actually refers to an object of type So we're combining two things:
Those two things conspire amazingly to ensure that almost everything "just works" in practice. That said, I would still prefer to have at least the option to express variance in a statically checked manner, like dart-lang/language#524 and dart-lang/language#753. |
at first i thought this was because
List
is invariant, but it turns out it's actually covariant (dart-lang/language#213), so i have no idea why this doesn't workThe text was updated successfully, but these errors were encountered: