-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Analyzer doesn't catch type errors and it's throwing errors in DDC runtime in the browser. #33447
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
Comments
It is well-known that the covariance rule for generic classes that Dart uses is not sound. The situation is the same as it has always been with arrays in Java and C#. It has been well-documented at least since this paper from 1989. So covariant arrays weren't introduced into Java or C# by accident, it was a well known trade-off (albeit a controversial one). Actually, Dart extended the unsound covariance to all generic classes, not just arrays, because it is sound for reading (which accounts for a large proportion of the usages of objects after a certain point in their lifetime), and it is dynamically checked for writing (such that you may get a run-time type error, but you won't get an inconsistent heap). So that's a very fundamental design choice for Dart: It has a type system that trades a certain amount of soundness for a "natural" subtype relationship which is sound for a substantial subset of the possible usages, with heap invariants enforced at run-time for the remaining ones. You could also say that Dart generics uses wildcarded types everywhere (because you'll actually get In particular, when you pass We have discussed introducing support for invariance (which could look like However, we have no concrete plans for when to introduce support for invariance (or contravariance for that matter), nor do we have a commitment to do it at all. #29777 may be used to push such things, but it is not universally considered to be an improvement, and when Dart was created it was actually an explicit goal to make it less complex for developers to handle than wildcards in Java. So I'd suggest that you start thinking about Dart generics as a mechanism that shares the degree of unsoundness that you (presumably) already know from arrays in other object-oriented languages, and then you can use various idioms to contain the unsoundness, and otherwise enjoy the added flexibility. ;-) To conclude, it is correct that the static analysis of your program is not sound (except that the actual type requirements are enforced at run time, so you won't get an inconsistent heap), but it is not something that happened by accident. So I'll close this issue with 'working as intended'. PS: Thanks for creating a repo showing exactly what is going on, that is always a great help when a topic is being explored! |
Hi @eernstg You mentioned c#, and for same example it shows compile time error: https://dotnetfiddle.net/b5jjoo , so can we expect the same from dart analyzer? Because it really distracting to find such errors in runtime, because both analyzer and dartdevc compiler says there is no error. Thanks in advance |
The execution of the example program must raise a dynamic error when an attempt is made to add a Developers won't get exactly the same behavior in every respect, e.g., because different hardware/configurations may have a different amounts of memory, etc. For With respect to https://dotnetfiddle.net/b5jjoo, the error says that a I mentioned that C# arrays are covariant, but C# generics in general do not support covariance. You can declare a particular formal type parameter as being covariant using So the I think the overall message here is that Dart uses covariant generics with dynamic checks to make this trade-off where programming is more flexible but less safe, just like those other languages always did with arrays. It's not an accident, and it's not sound, and lots of people jump to the conclusion that it's just wrong, but you may still get to like it in practice. ;-) |
Do we have a theoretical possibility to track such errors statically? (analyzer plugin or something else). We trying to migrate a huge application from dart 1.24.3 to dart 2.0 + DDC. And it's really hard to find errors in runtime on working application. |
Sure, it is not difficult to come up with a sound set of rules (e.g., Java or C# generics are both sound). One way to do that is to say that However, it is an undecidable problem to track anything like the dynamic flow of objects, so as soon as we do allow (and use) covariance as in Dart (that is, as soon as we have an upcast of the form But migration from Dart 1 to Dart 2 should not be affected by this kind of error because both Dart 1 and Dart 2 use covariant generics. However, there is one other issue to consider: In Dart 1 the type It can be tricky to find and fix all those situations where an object has a run-time type that contains If you have been using |
One more comment about covariant generics—I think there is a general software engineering structure that tends to help managing covariance such that you don't get run-time errors in practice: Many complex objects are created by an "owner" and populated during an initialization phase, and during the rest of their lifetime they are used in client code in an exclusively (or at least predominantly) read-only manner. This means that it is very easy for the class that implements the owner to maintain precise information about the actual type arguments (so we have invariance in practice, though without compiler enforcement), e.g., by using declarations like So if you can get used to a functional style (in the sense that objects are created during an initialization phase and henceforth used in a read-only manner) then covariance is just safe. We may of course still give developers the extra safety of being able to declare invariance (as in |
@eernstg I have made yet one example. The code throws an exception after the print statement in runtime. This is really inconvenient and unintuitive. import 'dart:async';
void main(){
final completer = new Completer<int>();
final future = completer.future;
future.catchError((Error e){
print(e);
});
new Timer(const Duration(milliseconds: 10), (){
print('runtime error right here');
completer.completeError(new CastError());
});
} Command is:
Here is the trace:
How I can get from trace, that error is in line before print statement? |
This is essentially a Dart-1-to-Dart-2 transition problem in the declared type. In the documentation for
So We don't have union types (cf. #59446 ;-), so we cannot just specify the desired constraint directly ( Various alternatives have been proposed for the typing of So the real problem here has to do with the transition from a very dynamic language (and recommended style) as in Dart 1, to an approach where static checks are supported as much as possible, as in Dart 2, and it is very specific to the individual method signature of Edit: Forgot to respond to this:
Currently the error will actually occur deep in the implementation of But it would certainly be possible for a lint to special-case |
I've created a request for a lint which could be helpful here: #57725. |
Oh, thank you! It's a great news. Waiting for a merge |
(PS: I don't know whether I have sufficient support for introducing such a lint to actually make it happen, I just think that it might be useful ;-) |
I just created a repo for the demo. Here is the code:
PS: I know how to fix it. But it's very inconvenient to find such errors in runtime. It would be awesome if you tell me how to see such errors in IDE (maybe analysis options or etc).
PS1: Code is working on DartVM
Errors in browser
Dart version: Dart VM version: 2.0.0-dev.62.0 (Wed Jun 13 16:50:22 2018 +0200) on "macos_x64"
dartdevc version 2.0.0-dev.62.0
Thank you in advance
The text was updated successfully, but these errors were encountered: