Breaking Change: --preview-dart-2 turned on by default

4,411 views
Skip to first unread message

Leaf Petersen

unread,
Mar 13, 2018, 1:20:12 AM3/13/18
to Flutter Dev

What is changing?

tldr: As part of Dart 2 release, we will be enabling --preview-dart-2 by default. This will cause some breakages due to stricter Dart type enforcement. The change is expected in master in the next few days, and will likely be propagated to our next beta channel build.

Introduction

Some weeks back the Dart team announced Dart 2, and we offered steps to preview Dart 2 with Flutter. We are now ready to turn on --preview-dart-2 by default. If you have not already tested your Flutter apps and packages, please do so now to see if you are impacted.

Breaking changes

Changes to Dart 2 type system include a compile-time component and a runtime component. We don’t anticipate any significant breakages from new compile-time errors introduced in Dart 2 because Flutter already opted into this part of Dart 2 by enabling strong mode in the analyzer. You may, however, see new runtime errors as we are now also enabling this part of Dart 2 semantics in VM.

Local type inference

What is changing?

Types of local variables, function literals and type arguments for method invocations and List/Map literals are inferred using strong mode type inference rules. For example, in Dart 2 this code that omits explicit variable types:

var x = 3;
var c = new Cat();
var l = [c];
var p = l.map((e) => e.parent());


is the same as:

int x = 3;
Cat c = new Cat();
List<Cat> l = <Cat>[c];
Iterable<Cat> p = l.map<Cat>((Cat e) => e.parent());


Underlined types are inferred by local type inference.

What is breaking?

In rare cases type inference will lead to runtime type check failures where they didn’t occur before:

class A {
}

class B extends A {
}

A foo(bool f) {
 var v = new B();
 if (f) {
   final a = new A();
   v = a;  // in Dart 2 throws: “type 'A' is not a subtype of type 'B'”

           // because variable v is inferred to have static type B,

           // and assigning a value of static type A to a variable of type B

           // is an implicit downcast.
 }
 return v;
}

void main() {
 foo(true);
}

How do I fix it?

To fix this kind of issue, simply declare types of local variables explicitly:

A foo(bool f) {
 A v = new B();
 if (f) {
   final a = new A();
   v = a;
 }
 return v;
}


Dynamic is not a wildcard type (bottom)

What is changing?

In Dart 1, the dynamic type was treated as a kind of wildcard type that could be used anywhere. For example:

  • a value of type List<dynamic> was assignable to List<String>

  • a function of type (int) → void was assignable to (dynamic) → void


void main() {
 final listOfInts = <dynamic>[0, 1, 2];
 sum(listOfInts);
 // in Dart 2 throws: type 'List' is not a subtype of type 'List<int>'

 dynamic f = (int x) => x * x;
 print(listOfInts.map(f).toList());
 // in Dart 2 throws: type '(int) => int' is not a subtype of type '(dynamic) => dynamic'
}

int sum(List<int> l) =>
 l.reduce((a, b) => a + b);

What is breaking?

The implications of this on Flutter are most visible around JSON processing and platform message channels:

import 'dart:convert';

void main() {
 final Map<String, int> m1 = JSON.decode("""{
 "a": 0,
 "b": 1
}""");
 // throws:
 //     type '_InternalLinkedHashMap<String, dynamic>' is not
 //     a subtype of type 'Map<String, int>'

 final Map<String, List<int>> m2 = JSON.decode("""{
 "a": [1, 2],
 "b": [3, 4]
}""");
 // throws:
 //     type '_InternalLinkedHashMap<String, dynamic>' is not
 //     a subtype of type 'Map<String, List<int>>'


A similar issue exists with Lists.

How do I fix it?

Use Map.cast, Map.retype, List.cast and List.retype to convert a List or Map into an appropriate type. Note that conversion is shallow and nested objects need to be converted separately.


import 'dart:convert';

void main() {
 final Map<String, int> m1 = JSON.decode("""{
 "a": 0,
 "b": 1
}""").retype<String, int>();

 final Map<String, dynamic> m2 = JSON.decode("""{
 "a": [1, 2],
 "b": [3, 4]
}""");

 // If you need tighter type then you need to convert
 // the map recursively.
 final Map<String, List<int>> m3 =
   new Map.fromIterable(m2.entries,
     key: (e) => e.key,
     value: (e) => e.value.cast<int>());
}


Run-time checks

What is changing?

Dart 2 has a sound type system with compile-time type checking (this has been available to Flutter developers for months). Soundness means that you can't encounter a value of the wrong type at runtime. To ensure this, runtime type checks are inserted by the compiler in potentially unsafe code. This can cause your code to throw a type error at runtime.

What is breaking?

By default, in Dart 2 you can assign an object to any location whose static type is a supertype or a subtype of the static type of the object:

final Object a = “hello”;
final int b = a;

// throws: Type 'String' is not a subtype of type 'int'

This is statically allowed, but at runtime, the assignment will be checked and will fail.  The most common causes of these kinds of failures are incorrect uses of generics:

List<dynamic> makeList(x, y) => [x, y];
final List<int> a = makeList(3, 4);

// throws: type 'List' is not a subtype of type 'List<int>'

How do I fix it?

Even though the list being returned contains only integers, the list was created as a List<dynamic> (and nothing stops non-integers from being added to it later), and so the runtime check on the assignment to a will fail.  Fixing these kinds of errors is usually best done by tracking down the place where the offending object was allocated, and arranging for it to allocated with the correct runtime type.  

List<dynamic> getList(x, y) => <int>[x, y];
final List<int> a = makeList(3, 4);


Generic methods can often help with this in the general case:

List<T> makeList<T>(T x, T y) => [x, y];
final List<int> a = makeList(3, 4);

More details

See this overview and this troubleshooting guide for more discussion, and feel free to follow up on mailing lists, chat rooms, or StackOverflow for help resolving issues.

Asynchronous functions start immediately

What is changing?

In Dart 2 functions that are marked as async will start to run immediately instead of being delayed by one microtask.

What is breaking?

If your code relies on the detailed timing aspects of asynchronous functions (which should be rare) you may be impacted; if not, you code will just run as-is, or slightly faster.

How do I fix it?

Please review the details in the previous announcement.

Reified generic methods

What is changing?

Type arguments of generic methods are reified in runtime


void pr<T>(T v) {
 print(T);
}


// explicitly provided
pr<String>("");  // prints: String
pr<int>(0);      // prints: int

// implicitly inferred
pr("");          // prints: String
pr(1);           // prints: int

What is breaking?

Code that assumes that types are not preserved at runtime. Prior to this change, the code above would print dynamic 4 times.

How do I fix it?

Rewrite the code to not assume that types will be erased.

Non-breaking changes

Optional new/const

What is changing?

new and const may be omitted when calling a constructor


// Dart 1
Widget build(BuildContext context) =>
 new Container(
   height: 56.0,
   padding: const EdgeInsets.symmetric(horizontal: 8.0),
   decoration: new BoxDecoration(color: Colors.blue[500]),
   child: new Row(
     ...
   ),
 );

// Dart 2
Widget build(BuildContext context) =>
 Container(
   height: 56.0,
   padding: EdgeInsets.symmetric(horizontal: 8.0),
   decoration: BoxDecoration(color: Colors.blue[500]),
   child: Row(
     ...
   ),
 );


When is this expected?

The change is expected to land later this week in the Master channel, and will then be propagated to the various Flutter channels over the following days and weeks.


What will break, and how do I fix it?

All the type changes listed above under the ‘Breaking changes’ heading have the potential to break your existing application or package. Our recommendation is to asap test your code with the preview flag:


  1. Upgrade to flutter beta 1 or later

  2. Run your app with the preview flag enabled on

  3. Keep an eye out for typing issues printed to the console during compilation, and during execution, of the application

  4. Resolve the issues as per the details at the top of this post

Why is this change being made?

Dart 2 is an update to the Dart programming language that introduces a number of improvements to the Dart language, such as:

  • Strong compile-time type checks

  • A complete run-time typing system (the primary content of the present post)

  • Optional new/const keyword when calling a constructor (e.g., change child: new Text('Hi') to child: Text('Hi'))

  • Various smaller language changes

The changes are now mature enough to land in Flutter.


We will send an update here when this change lands.  Please send an email to flutter-dev@ for any issues or questions.



Thanks,

Leaf and Michael


Michael Thomsen

unread,
Mar 18, 2018, 12:43:09 PM3/18/18
to Leaf Petersen, Flutter Dev
Hello everyone, quick update: This has now landed in the master channel. 

We expect to roll it to the dev shortly, and hope to have it available in the next beta roll as-well.

--
You received this message because you are subscribed to the Google Groups "Flutter Dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to flutter-dev+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
Message has been deleted
0 new messages