mirror of
https://github.com/dart-lang/sdk
synced 2024-10-01 19:29:09 +00:00
Add 1.22 features to CHANGELOG.
R=eernst@google.com, kevmoo@google.com, lrn@google.com, mit@google.com Review-Url: https://codereview.chromium.org/2648203003 .
This commit is contained in:
parent
154cd39db8
commit
c3f1212a9c
189
CHANGELOG.md
189
CHANGELOG.md
|
@ -11,7 +11,8 @@
|
|||
* Breaking change: ['Generalized tear-offs'](https://github.com/gbracha/generalizedTearOffs/blob/master/proposal.md)
|
||||
are no longer supported, and will cause errors. We updated the language spec
|
||||
and added warnings in 1.21, and are now taking the last step to fully
|
||||
de-support them. They were previously supported in the VM only.
|
||||
de-support them. They were previously only supported in the VM, and there
|
||||
are almost no known uses of them in the wild.
|
||||
|
||||
* The `assert()` statement has been expanded to support an optional second
|
||||
`message` argument (SDK issue [27342](https://github.com/dart-lang/sdk/issues/27342)).
|
||||
|
@ -36,19 +37,187 @@
|
|||
```
|
||||
|
||||
* The `Null` type has been moved to the bottom of the type hierarchy. As such,
|
||||
it is considered a subtype of every other type.
|
||||
it is considered a subtype of every other type. The `null` *literal* was
|
||||
always treated as a bottom type. Now the named class `Null` is too:
|
||||
|
||||
Examples:
|
||||
```
|
||||
Null foo() => null;
|
||||
int x = foo();
|
||||
String x = foo();
|
||||
```dart
|
||||
const empty = <Null>[];
|
||||
|
||||
List<Null> bar() => <Null>[];
|
||||
List<int> = bar();
|
||||
List<String> = bar();
|
||||
String concatenate(List<String> parts) => parts.join();
|
||||
int sum(List<int> numbers) => numbers.fold(0, (sum, n) => sum + n);
|
||||
|
||||
concatenate(empty); // OK.
|
||||
sum(empty); // OK.
|
||||
```
|
||||
|
||||
* Introduce `covariant` modifier on parameters. It indicates that the
|
||||
parameter (and the corresponding parameter in any method that overrides it)
|
||||
has looser override rules. In strong mode, these require a runtime type
|
||||
check to maintain soundness, but enable an architectural pattern that is
|
||||
useful in some code.
|
||||
|
||||
It lets you specialize a family of classes together, like so:
|
||||
|
||||
```dart
|
||||
abstract class Predator {
|
||||
void chaseAndEat(covariant Prey p);
|
||||
}
|
||||
|
||||
abstract class Prey {}
|
||||
|
||||
class Mouse extends Prey {}
|
||||
|
||||
class Seal extends Prey {}
|
||||
|
||||
class Cat extends Predator {
|
||||
void chaseAndEat(Mouse m) => ...
|
||||
}
|
||||
|
||||
class Orca extends Predator {
|
||||
void chaseAndEat(Seal s) => ...
|
||||
}
|
||||
```
|
||||
|
||||
This isn't statically safe, because you could do:
|
||||
|
||||
```dart
|
||||
Predator predator = new Cat(); // Upcast.
|
||||
predator(new Seal()); // Cats can't eat seals!
|
||||
```
|
||||
|
||||
To preserve soundness in strong mode, in the body of a method that uses a
|
||||
covariant override (here, `Cat.chaseAndEat()`), the compiler automatically
|
||||
inserts a check that the parameter is of the expected type. So the compiler
|
||||
gives you something like:
|
||||
|
||||
```dart
|
||||
class Cat extends Predator {
|
||||
void chaseAndEat(o) {
|
||||
var m = o as Mouse;
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Spec mode allows this unsound behavior on all parameters, even though users
|
||||
rarely rely on it. Strong mode disallowed it initially. Now, strong mode
|
||||
lets you opt into this behavior in the places where you do want it by using
|
||||
this modifier. Outside of strong mode, the modifier is ignored.
|
||||
|
||||
* Change instantiate-to-bounds rules for generic type parameters when running
|
||||
in strong mode. If you leave off the type parameters from a generic type, we
|
||||
need to decide what to fill them in with. Dart 1.0 says just use `dynamic`,
|
||||
but that isn't sound:
|
||||
|
||||
```dart
|
||||
class Abser<T extends num> {
|
||||
void absThis(T n) { n.abs(); }
|
||||
}
|
||||
|
||||
var a = new Abser(); // Abser<dynamic>.
|
||||
a.absThis("not a num");
|
||||
```
|
||||
|
||||
We want the body of `absThis()` to be able to safely assume `n` is at
|
||||
least a `num` -- that's why there's a constraint on T, after all. Implicitly
|
||||
using `dynamic` as the type parameter in this example breaks that.
|
||||
|
||||
Instead, strong mode uses the bound. In the above example, it fills it in
|
||||
with `num`, and then the second line where a string is passed becomes a
|
||||
static error.
|
||||
|
||||
However, there are some cases where it is hard to figure out what that
|
||||
default bound should be:
|
||||
|
||||
```dart
|
||||
class RuhRoh<T extends Comparable<T>> {}
|
||||
```
|
||||
|
||||
Strong mode's initial behavior sometimes produced surprising, unintended
|
||||
results. For 1.22, we take a simpler approach and then report an error if
|
||||
a good default type argument can't be found.
|
||||
|
||||
### Core libraries
|
||||
|
||||
* Define `FutureOr<T>` for code that works with either a future or an
|
||||
immediate value of some type. For example, say you do a lot of text
|
||||
manipulation, and you want a handy function to chain a bunch of them:
|
||||
|
||||
```dart
|
||||
typedef String StringSwizzler(String input);
|
||||
|
||||
String swizzle(String input, List<StringSwizzler> swizzlers) {
|
||||
var result = input;
|
||||
for (var swizzler in swizzlers) {
|
||||
result = swizzler(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
This works fine:
|
||||
|
||||
```dart
|
||||
main() {
|
||||
var result = swizzle("input", [
|
||||
(s) => s.toUpperCase(),
|
||||
(s) => () => s * 2)
|
||||
]);
|
||||
print(result); // "INPUTINPUT".
|
||||
}
|
||||
```
|
||||
|
||||
Later, you realize you'd also like to support swizzlers that are
|
||||
asynchronous (maybe they look up synonyms for words online). You could make
|
||||
your API strictly asynchronous, but then users of simple synchronous
|
||||
swizzlers have to manually wrap the return value in a `Future.value()`.
|
||||
Ideally, your `swizzle()` function would be "polymorphic over asynchrony".
|
||||
It would allow both synchronous and asynchronous swizzlers. Because `await`
|
||||
accepts immediate values, it is easy to implement this dynamically:
|
||||
|
||||
```dart
|
||||
Future<String> swizzle(String input, List<StringSwizzler> swizzlers) async {
|
||||
var result = input;
|
||||
for (var swizzler in swizzlers) {
|
||||
result = await swizzler(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
main() async {
|
||||
var result = swizzle("input", [
|
||||
(s) => s.toUpperCase(),
|
||||
(s) => new Future.delayed(new Duration(milliseconds: 40), () => s * 2)
|
||||
]);
|
||||
print(await result);
|
||||
}
|
||||
```
|
||||
|
||||
What should the declared return type on StringSwizzler be? In the past, you
|
||||
had to use `dynamic` or `Object`, but that doesn't tell the user much. Now,
|
||||
you can do:
|
||||
|
||||
```dart
|
||||
typedef FutureOr<String> StringSwizzler(String input);
|
||||
```
|
||||
|
||||
Like the name implies, `FutureOr<String>` is a union type. It can be a
|
||||
`String` or a `Future<String>`, but not anything else. In this case, that's
|
||||
not super useful beyond just stating a more precise type for readers of the
|
||||
code. It does give you a little better error checking in code that uses the
|
||||
result of that.
|
||||
|
||||
`FutureOr<T>` becomes really important in *generic* methods like
|
||||
`Future.then()`. In those cases, having the type system understand this
|
||||
magical union type helps type inference figure out the type argument of
|
||||
`then()` based on the closure you pass it.
|
||||
|
||||
Previously, strong mode had hard-coded rules for handling `Future.then()`
|
||||
specifically. `FutureOr<T>` exposes that functionality so third-party APIs
|
||||
can take advantage of it too.
|
||||
|
||||
### Tool changes
|
||||
|
||||
* Dart2Js
|
||||
|
|
Loading…
Reference in a new issue